[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [pkieltyka] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "on:\n  push:\n    branches: \"**\"\n    paths-ignore:\n      - \"docs/**\"\n  pull_request:\n    branches: \"**\"\n    paths-ignore:\n      - \"docs/**\"\n\nname: Test\njobs:\n  test:\n    env:\n      GOPATH: ${{ github.workspace }}\n\n    defaults:\n      run:\n        working-directory: ${{ env.GOPATH }}/src/github.com/${{ github.repository }}\n\n    strategy:\n      matrix:\n        go-version: [1.23.x, 1.24.x, 1.25.x, 1.26.x]\n        os: [ubuntu-latest, windows-latest]\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go-version }}\n          check-latest: true\n          cache: false\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          path: ${{ env.GOPATH }}/src/github.com/${{ github.repository }}\n      - name: Test\n        run: |\n          go get -d -t ./...\n          make test\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n*.sw?\n.vscode\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## v5.0.12 (2024-02-16)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.11...v5.0.12\n\n\n## v5.0.11 (2023-12-19)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.10...v5.0.11\n\n\n## v5.0.10 (2023-07-13)\n\n- Fixed small edge case in tests of v5.0.9 for older Go versions\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.9...v5.0.10\n\n\n## v5.0.9 (2023-07-13)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.8...v5.0.9\n\n\n## v5.0.8 (2022-12-07)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.7...v5.0.8\n\n\n## v5.0.7 (2021-11-18)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.6...v5.0.7\n\n\n## v5.0.6 (2021-11-15)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.5...v5.0.6\n\n\n## v5.0.5 (2021-10-27)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.4...v5.0.5\n\n\n## v5.0.4 (2021-08-29)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.3...v5.0.4\n\n\n## v5.0.3 (2021-04-29)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.2...v5.0.3\n\n\n## v5.0.2 (2021-03-25)\n\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.1...v5.0.2\n\n\n## v5.0.1 (2021-03-10)\n\n- Small improvements\n- History of changes: see https://github.com/go-chi/chi/compare/v5.0.0...v5.0.1\n\n\n## v5.0.0 (2021-02-27)\n\n- chi v5, `github.com/go-chi/chi/v5` introduces the adoption of Go's SIV to adhere to the current state-of-the-tools in Go.\n- chi v1.5.x did not work out as planned, as the Go tooling is too powerful and chi's adoption is too wide.\n  The most responsible thing to do for everyone's benefit is to just release v5 with SIV, so I present to you all,\n  chi v5 at `github.com/go-chi/chi/v5`. I hope someday the developer experience and ergonomics I've been seeking\n  will still come to fruition in some form, see https://github.com/golang/go/issues/44550\n- History of changes: see https://github.com/go-chi/chi/compare/v1.5.4...v5.0.0\n\n\n## v1.5.4 (2021-02-27)\n\n- Undo prior retraction in v1.5.3 as we prepare for v5.0.0 release\n- History of changes: see https://github.com/go-chi/chi/compare/v1.5.3...v1.5.4\n\n\n## v1.5.3 (2021-02-21)\n\n- Update go.mod to go 1.16 with new retract directive marking all versions without prior go.mod support\n- History of changes: see https://github.com/go-chi/chi/compare/v1.5.2...v1.5.3\n\n\n## v1.5.2 (2021-02-10)\n\n- Reverting allocation optimization as a precaution as go test -race fails.\n- Minor improvements, see history below\n- History of changes: see https://github.com/go-chi/chi/compare/v1.5.1...v1.5.2\n\n\n## v1.5.1 (2020-12-06)\n\n- Performance improvement: removing 1 allocation by foregoing context.WithValue, thank you @bouk for\n  your contribution (https://github.com/go-chi/chi/pull/555). Note: new benchmarks posted in README.\n- `middleware.CleanPath`: new middleware that clean's request path of double slashes\n- deprecate & remove `chi.ServerBaseContext` in favour of stdlib `http.Server#BaseContext`\n- plus other tiny improvements, see full commit history below\n- History of changes: see https://github.com/go-chi/chi/compare/v4.1.2...v1.5.1\n\n\n## v1.5.0 (2020-11-12) - now with go.mod support\n\n`chi` dates back to 2016 with it's original implementation as one of the first routers to adopt the newly introduced\ncontext.Context api to the stdlib -- set out to design a router that is faster, more modular and simpler than anything\nelse out there -- while not introducing any custom handler types or dependencies. Today, `chi` still has zero dependencies,\nand in many ways is future proofed from changes, given it's minimal nature. Between versions, chi's iterations have been very\nincremental, with the architecture and api being the same today as it was originally designed in 2016. For this reason it \nmakes chi a pretty easy project to maintain, as well thanks to the many amazing community contributions over the years\nto who all help make chi better (total of 86 contributors to date -- thanks all!).\n\nChi has been a labour of love, art and engineering, with the goals to offer beautiful ergonomics, flexibility, performance\nand simplicity when building HTTP services with Go. I've strived to keep the router very minimal in surface area / code size,\nand always improving the code wherever possible -- and as of today the `chi` package is just 1082 lines of code (not counting\nmiddlewares, which are all optional). As well, I don't have the exact metrics, but from my analysis and email exchanges from\ncompanies and developers, chi is used by thousands of projects around the world -- thank you all as there is no better form of\njoy for me than to have art I had started be helpful and enjoyed by others. And of course I use chi in all of my own projects too :)\n\nFor me, the aesthetics of chi's code and usage are very important. With the introduction of Go's module support\n(which I'm a big fan of), chi's past versioning scheme choice to v2, v3 and v4 would mean I'd require the import path\nof \"github.com/go-chi/chi/v4\", leading to the lengthy discussion at https://github.com/go-chi/chi/issues/462.\nHaha, to some, you may be scratching your head why I've spent > 1 year stalling to adopt \"/vXX\" convention in the import\npath -- which isn't horrible in general -- but for chi, I'm unable to accept it as I strive for perfection in it's API design,\naesthetics and simplicity. It just doesn't feel good to me given chi's simple nature -- I do not foresee a \"v5\" or \"v6\",\nand upgrading between versions in the future will also be just incremental.\n\nI do understand versioning is a part of the API design as well, which is why the solution for a while has been to \"do nothing\",\nas Go supports both old and new import paths with/out go.mod. However, now that Go module support has had time to iron out kinks and\nis adopted everywhere, it's time for chi to get with the times. Luckily, I've discovered a path forward that will make me happy,\nwhile also not breaking anyone's app who adopted a prior versioning from tags in v2/v3/v4. I've made an experimental release of\nv1.5.0 with go.mod silently, and tested it with new and old projects, to ensure the developer experience is preserved, and it's\nlargely unnoticed. Fortunately, Go's toolchain will check the tags of a repo and consider the \"latest\" tag the one with go.mod.\nHowever, you can still request a specific older tag such as v4.1.2, and everything will \"just work\". But new users can just\n`go get github.com/go-chi/chi` or `go get github.com/go-chi/chi@latest` and they will get the latest version which contains\ngo.mod support, which is v1.5.0+. `chi` will not change very much over the years, just like it hasn't changed much from 4 years ago.\nTherefore, we will stay on v1.x from here on, starting from v1.5.0. Any breaking changes will bump a \"minor\" release and\nbackwards-compatible improvements/fixes will bump a \"tiny\" release.\n\nFor existing projects who want to upgrade to the latest go.mod version, run: `go get -u github.com/go-chi/chi@v1.5.0`,\nwhich will get you on the go.mod version line (as Go's mod cache may still remember v4.x). Brand new systems can run\n`go get -u github.com/go-chi/chi` or `go get -u github.com/go-chi/chi@latest` to install chi, which will install v1.5.0+\nbuilt with go.mod support.\n\nMy apologies to the developers who will disagree with the decisions above, but, hope you'll try it and see it's a very\nminor request which is backwards compatible and won't break your existing installations.\n\nCheers all, happy coding!\n\n\n---\n\n\n## v4.1.2 (2020-06-02)\n\n- fix that handles MethodNotAllowed with path variables, thank you @caseyhadden for your contribution\n- fix to replace nested wildcards correctly in RoutePattern, thank you @@unmultimedio for your contribution\n- History of changes: see https://github.com/go-chi/chi/compare/v4.1.1...v4.1.2\n\n\n## v4.1.1 (2020-04-16)\n\n- fix for issue https://github.com/go-chi/chi/issues/411 which allows for overlapping regexp\n  route to the correct handler through a recursive tree search, thanks to @Jahaja for the PR/fix!\n- new middleware.RouteHeaders as a simple router for request headers with wildcard support\n- History of changes: see https://github.com/go-chi/chi/compare/v4.1.0...v4.1.1\n\n\n## v4.1.0 (2020-04-1)\n\n- middleware.LogEntry: Write method on interface now passes the response header\n  and an extra interface type useful for custom logger implementations.\n- middleware.WrapResponseWriter: minor fix\n- middleware.Recoverer: a bit prettier\n- History of changes: see https://github.com/go-chi/chi/compare/v4.0.4...v4.1.0\n\n## v4.0.4 (2020-03-24)\n\n- middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496)\n- a few minor improvements and fixes\n- History of changes: see https://github.com/go-chi/chi/compare/v4.0.3...v4.0.4\n\n\n## v4.0.3 (2020-01-09)\n\n- core: fix regexp routing to include default value when param is not matched\n- middleware: rewrite of middleware.Compress\n- middleware: suppress http.ErrAbortHandler in middleware.Recoverer\n- History of changes: see https://github.com/go-chi/chi/compare/v4.0.2...v4.0.3\n\n\n## v4.0.2 (2019-02-26)\n\n- Minor fixes\n- History of changes: see https://github.com/go-chi/chi/compare/v4.0.1...v4.0.2\n\n\n## v4.0.1 (2019-01-21)\n\n- Fixes issue with compress middleware: #382 #385\n- History of changes: see https://github.com/go-chi/chi/compare/v4.0.0...v4.0.1\n\n\n## v4.0.0 (2019-01-10)\n\n- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8\n- router: respond with 404 on router with no routes (#362)\n- router: additional check to ensure wildcard is at the end of a url pattern (#333)\n- middleware: deprecate use of http.CloseNotifier (#347)\n- middleware: fix RedirectSlashes to include query params on redirect (#334)\n- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0\n\n\n## v3.3.4 (2019-01-07)\n\n- Minor middleware improvements. No changes to core library/router. Moving v3 into its\n- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11\n- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4\n\n\n## v3.3.3 (2018-08-27)\n\n- Minor release\n- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3\n\n\n## v3.3.2 (2017-12-22)\n\n- Support to route trailing slashes on mounted sub-routers (#281)\n- middleware: new `ContentCharset` to check matching charsets. Thank you\n  @csucu for your community contribution!\n\n\n## v3.3.1 (2017-11-20)\n\n- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types\n- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value\n- Minor bug fixes\n\n\n## v3.3.0 (2017-10-10)\n\n- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage\n- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod(\"LINK\")` and `chi.RegisterMethod(\"UNLINK\")` in an `init()` function\n\n\n## v3.2.1 (2017-08-31)\n\n- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface\n  and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path\n- Add new `RouteMethod` to `*Context`\n- Add new `Routes` pointer to `*Context`\n- Add new `middleware.GetHead` to route missing HEAD requests to GET handler\n- Updated benchmarks (see README)\n\n\n## v3.1.5 (2017-08-02)\n\n- Setup golint and go vet for the project\n- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler`\n  to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler`\n\n\n## v3.1.0 (2017-07-10)\n\n- Fix a few minor issues after v3 release\n- Move `docgen` sub-pkg to https://github.com/go-chi/docgen\n- Move `render` sub-pkg to https://github.com/go-chi/render\n- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime \n  suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in\n  https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage.\n\n\n## v3.0.0 (2017-06-21)\n\n- Major update to chi library with many exciting updates, but also some *breaking changes*\n- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as\n  `/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the\n  same router\n- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example:\n  `r.Get(\"/articles/{name:[a-z]+}\", h)` and `chi.URLParam(r, \"name\")`\n- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as\n  `r.Method(\"GET\", \"/\", h)` which provides a cleaner interface for custom handlers like\n  in `_examples/custom-handler`\n- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their\n  own using file handler with the stdlib, see `_examples/fileserver` for an example\n- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()`\n- Moved the chi project to its own organization, to allow chi-related community packages to\n  be easily discovered and supported, at: https://github.com/go-chi\n- *NOTE:* please update your import paths to `\"github.com/go-chi/chi\"`\n- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2\n\n\n## v2.1.0 (2017-03-30)\n\n- Minor improvements and update to the chi core library\n- Introduced a brand new `chi/render` sub-package to complete the story of building\n  APIs to offer a pattern for managing well-defined request / response payloads. Please\n  check out the updated `_examples/rest` example for how it works.\n- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface\n\n\n## v2.0.0 (2017-01-06)\n\n- After many months of v2 being in an RC state with many companies and users running it in\n  production, the inclusion of some improvements to the middlewares, we are very pleased to\n  announce v2.0.0 of chi.\n\n\n## v2.0.0-rc1 (2016-07-26)\n\n- Huge update! chi v2 is a large refactor targeting Go 1.7+. As of Go 1.7, the popular\n  community `\"net/context\"` package has been included in the standard library as `\"context\"` and\n  utilized by `\"net/http\"` and `http.Request` to managing deadlines, cancelation signals and other\n  request-scoped values. We're very excited about the new context addition and are proud to\n  introduce chi v2, a minimal and powerful routing package for building large HTTP services,\n  with zero external dependencies. Chi focuses on idiomatic design and encourages the use of \n  stdlib HTTP handlers and middlewares.\n- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc`\n- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()`\n- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`,\n  which provides direct access to URL routing parameters, the routing path and the matching\n  routing patterns.\n- Users upgrading from chi v1 to v2, need to:\n  1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to\n     the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)`\n  2. Use `chi.URLParam(r *http.Request, paramKey string) string`\n     or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value\n\n\n## v1.0.0 (2016-07-01)\n\n- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older.\n\n\n## v0.9.0 (2016-03-31)\n\n- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33)\n- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)[\"id\"]` used to access url parameters\n  has changed to: `chi.URLParam(ctx, \"id\")`\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\n## Prerequisites\n\n1. [Install Go][go-install].\n2. Download the sources and switch the working directory:\n\n    ```bash\n    go get -u -d github.com/go-chi/chi\n    cd $GOPATH/src/github.com/go-chi/chi\n    ```\n\n## Submitting a Pull Request\n\nA typical workflow is:\n\n1. [Fork the repository.][fork]\n2. [Create a topic branch.][branch]\n3. Add tests for your change.\n4. Run `go test`. If your tests pass, return to the step 3.\n5. Implement the change and ensure the steps from the previous step pass.\n6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline.\n7. [Add, commit and push your changes.][git-help]\n8. [Submit a pull request.][pull-req]\n\n[go-install]: https://golang.org/doc/install\n[fork]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo\n[branch]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches \n[git-help]: https://docs.github.com/en\n[pull-req]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests\n\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc.\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: all\nall:\n\t@echo \"**********************************************************\"\n\t@echo \"**                    chi build tool                    **\"\n\t@echo \"**********************************************************\"\n\n\n.PHONY: test\ntest:\n\tgo clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware\n\n.PHONY: test-router\ntest-router:\n\tgo test -race -v .\n\n.PHONY: test-middleware\ntest-middleware:\n\tgo test -race -v ./middleware\n\n.PHONY: docs\ndocs:\n\tnpx docsify-cli serve ./docs\n"
  },
  {
    "path": "README.md",
    "content": "# <img alt=\"chi\" src=\"https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg\" width=\"220\" />\n\n\n[![GoDoc Widget]][GoDoc]\n\n`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's\nespecially good at helping you write large REST API services that are kept maintainable as your\nproject grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to\nhandle signaling, cancelation and request-scoped values across a handler chain.\n\nThe focus of the project has been to seek out an elegant and comfortable design for writing\nREST API servers, written during the development of the Pressly API service that powers our\npublic API service, which in turn powers all of our client-side applications.\n\nThe key considerations of chi's design are: project structure, maintainability, standard http\nhandlers (stdlib-only), developer productivity, and deconstructing a large system into many small\nparts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also\nincluded some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render)\nand [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!\n\n## Install\n\n```sh\ngo get -u github.com/go-chi/chi/v5\n```\n\n\n## Features\n\n* **Lightweight** - cloc'd in ~1000 LOC for the chi router\n* **Fast** - yes, see [benchmarks](#benchmarks)\n* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http`\n* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and sub-router mounting\n* **Context control** - built on new `context` package, providing value chaining, cancellations and timeouts\n* **Robust** - in production at Pressly, Cloudflare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91))\n* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown\n* **Go.mod support** - as of v5, go.mod support (see [CHANGELOG](https://github.com/go-chi/chi/blob/master/CHANGELOG.md))\n* **No external dependencies** - plain ol' Go stdlib + net/http\n\n\n## Examples\n\nSee [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples.\n\n\n**As easy as:**\n\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\tr.Use(middleware.Logger)\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"welcome\"))\n\t})\n\thttp.ListenAndServe(\":3000\", r)\n}\n```\n\n**REST Preview:**\n\nHere is a little preview of what routing looks like with chi. Also take a look at the generated routing docs\nin JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in\nMarkdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)).\n\nI highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed\nabove, they will show you all the features of chi and serve as a good form of documentation.\n\n```go\nimport (\n  //...\n  \"context\"\n  \"github.com/go-chi/chi/v5\"\n  \"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc main() {\n  r := chi.NewRouter()\n\n  // A good base middleware stack\n  r.Use(middleware.RequestID)\n  r.Use(middleware.RealIP)\n  r.Use(middleware.Logger)\n  r.Use(middleware.Recoverer)\n\n  // Set a timeout value on the request context (ctx), that will signal\n  // through ctx.Done() that the request has timed out and further\n  // processing should be stopped.\n  r.Use(middleware.Timeout(60 * time.Second))\n\n  r.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n    w.Write([]byte(\"hi\"))\n  })\n\n  // RESTy routes for \"articles\" resource\n  r.Route(\"/articles\", func(r chi.Router) {\n    r.With(paginate).Get(\"/\", listArticles)                           // GET /articles\n    r.With(paginate).Get(\"/{month}-{day}-{year}\", listArticlesByDate) // GET /articles/01-16-2017\n\n    r.Post(\"/\", createArticle)                                        // POST /articles\n    r.Get(\"/search\", searchArticles)                                  // GET /articles/search\n\n    // Regexp url parameters:\n    r.Get(\"/{articleSlug:[a-z-]+}\", getArticleBySlug)                // GET /articles/home-is-toronto\n\n    // Subrouters:\n    r.Route(\"/{articleID}\", func(r chi.Router) {\n      r.Use(ArticleCtx)\n      r.Get(\"/\", getArticle)                                          // GET /articles/123\n      r.Put(\"/\", updateArticle)                                       // PUT /articles/123\n      r.Delete(\"/\", deleteArticle)                                    // DELETE /articles/123\n    })\n  })\n\n  // Mount the admin sub-router\n  r.Mount(\"/admin\", adminRouter())\n\n  http.ListenAndServe(\":3333\", r)\n}\n\nfunc ArticleCtx(next http.Handler) http.Handler {\n  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    articleID := chi.URLParam(r, \"articleID\")\n    article, err := dbGetArticle(articleID)\n    if err != nil {\n      http.Error(w, http.StatusText(404), 404)\n      return\n    }\n    ctx := context.WithValue(r.Context(), \"article\", article)\n    next.ServeHTTP(w, r.WithContext(ctx))\n  })\n}\n\nfunc getArticle(w http.ResponseWriter, r *http.Request) {\n  ctx := r.Context()\n  article, ok := ctx.Value(\"article\").(*Article)\n  if !ok {\n    http.Error(w, http.StatusText(422), 422)\n    return\n  }\n  w.Write([]byte(fmt.Sprintf(\"title:%s\", article.Title)))\n}\n\n// A completely separate router for administrator routes\nfunc adminRouter() http.Handler {\n  r := chi.NewRouter()\n  r.Use(AdminOnly)\n  r.Get(\"/\", adminIndex)\n  r.Get(\"/accounts\", adminListAccounts)\n  return r\n}\n\nfunc AdminOnly(next http.Handler) http.Handler {\n  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    ctx := r.Context()\n    perm, ok := ctx.Value(\"acl.permission\").(YourPermissionType)\n    if !ok || !perm.IsAdmin() {\n      http.Error(w, http.StatusText(403), 403)\n      return\n    }\n    next.ServeHTTP(w, r)\n  })\n}\n```\n\n\n## Router interface\n\nchi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree).\nThe router is fully compatible with `net/http`.\n\nBuilt on top of the tree is the `Router` interface:\n\n```go\n// Router consisting of the core routing methods used by chi's Mux,\n// using only the standard net/http.\ntype Router interface {\n\thttp.Handler\n\tRoutes\n\n\t// Use appends one or more middlewares onto the Router stack.\n\tUse(middlewares ...func(http.Handler) http.Handler)\n\n\t// With adds inline middlewares for an endpoint handler.\n\tWith(middlewares ...func(http.Handler) http.Handler) Router\n\n\t// Group adds a new inline-Router along the current routing\n\t// path, with a fresh middleware stack for the inline-Router.\n\tGroup(fn func(r Router)) Router\n\n\t// Route mounts a sub-Router along a `pattern` string.\n\tRoute(pattern string, fn func(r Router)) Router\n\n\t// Mount attaches another http.Handler along ./pattern/*\n\tMount(pattern string, h http.Handler)\n\n\t// Handle and HandleFunc adds routes for `pattern` that matches\n\t// all HTTP methods.\n\tHandle(pattern string, h http.Handler)\n\tHandleFunc(pattern string, h http.HandlerFunc)\n\n\t// Method and MethodFunc adds routes for `pattern` that matches\n\t// the `method` HTTP method.\n\tMethod(method, pattern string, h http.Handler)\n\tMethodFunc(method, pattern string, h http.HandlerFunc)\n\n\t// HTTP-method routing along `pattern`\n\tConnect(pattern string, h http.HandlerFunc)\n\tDelete(pattern string, h http.HandlerFunc)\n\tGet(pattern string, h http.HandlerFunc)\n\tHead(pattern string, h http.HandlerFunc)\n\tOptions(pattern string, h http.HandlerFunc)\n\tPatch(pattern string, h http.HandlerFunc)\n\tPost(pattern string, h http.HandlerFunc)\n\tPut(pattern string, h http.HandlerFunc)\n\tTrace(pattern string, h http.HandlerFunc)\n\n\t// NotFound defines a handler to respond whenever a route could\n\t// not be found.\n\tNotFound(h http.HandlerFunc)\n\n\t// MethodNotAllowed defines a handler to respond whenever a method is\n\t// not allowed.\n\tMethodNotAllowed(h http.HandlerFunc)\n}\n\n// Routes interface adds two methods for router traversal, which is also\n// used by the github.com/go-chi/docgen package to generate documentation for Routers.\ntype Routes interface {\n\t// Routes returns the routing tree in an easily traversable structure.\n\tRoutes() []Route\n\n\t// Middlewares returns the list of middlewares in use by the router.\n\tMiddlewares() Middlewares\n\n\t// Match searches the routing tree for a handler that matches\n\t// the method/path - similar to routing a http request, but without\n\t// executing the handler thereafter.\n\tMatch(rctx *Context, method, path string) bool\n}\n```\n\nEach routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern\nsupports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters\ncan be fetched at runtime by calling `chi.URLParam(r, \"userID\")` for named parameters\nand `chi.URLParam(r, \"*\")` for a wildcard parameter.\n\n\n### Middleware handlers\n\nchi's middlewares are just stdlib net/http middleware handlers. There is nothing special\nabout them, which means the router and all the tooling is designed to be compatible and\nfriendly with any middleware in the community. This offers much better extensibility and reuse\nof packages and is at the heart of chi's purpose.\n\nHere is an example of a standard net/http middleware where we assign a context key `\"user\"`\nthe value of `\"123\"`. This middleware sets a hypothetical user identifier on the request\ncontext and calls the next handler in the chain.\n\n```go\n// HTTP middleware setting a value on the request context\nfunc MyMiddleware(next http.Handler) http.Handler {\n  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    // create new context from `r` request context, and assign key `\"user\"`\n    // to value of `\"123\"`\n    ctx := context.WithValue(r.Context(), \"user\", \"123\")\n\n    // call the next handler in the chain, passing the response writer and\n    // the updated request object with the new context value.\n    //\n    // note: context.Context values are nested, so any previously set\n    // values will be accessible as well, and the new `\"user\"` key\n    // will be accessible from this point forward.\n    next.ServeHTTP(w, r.WithContext(ctx))\n  })\n}\n```\n\n\n### Request handlers\n\nchi uses standard net/http request handlers. This little snippet is an example of a http.Handler\nfunc that reads a user identifier from the request context - hypothetically, identifying\nthe user sending an authenticated request, validated+set by a previous middleware handler.\n\n```go\n// HTTP handler accessing data from the request context.\nfunc MyRequestHandler(w http.ResponseWriter, r *http.Request) {\n  // here we read from the request context and fetch out `\"user\"` key set in\n  // the MyMiddleware example above.\n  user := r.Context().Value(\"user\").(string)\n\n  // respond to the client\n  w.Write([]byte(fmt.Sprintf(\"hi %s\", user)))\n}\n```\n\n\n### URL parameters\n\nchi's router parses and stores URL parameters right onto the request context. Here is\nan example of how to access URL params in your net/http handlers. And of course, middlewares\nare able to access the same information.\n\n```go\n// HTTP handler accessing the url routing parameters.\nfunc MyRequestHandler(w http.ResponseWriter, r *http.Request) {\n  // fetch the url parameter `\"userID\"` from the request of a matching\n  // routing pattern. An example routing pattern could be: /users/{userID}\n  userID := chi.URLParam(r, \"userID\")\n\n  // fetch `\"key\"` from the request context\n  ctx := r.Context()\n  key := ctx.Value(\"key\").(string)\n\n  // respond to the client\n  w.Write([]byte(fmt.Sprintf(\"hi %v, %v\", userID, key)))\n}\n```\n\n\n## Middlewares\n\nchi comes equipped with an optional `middleware` package, providing a suite of standard\n`net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible\nwith `net/http` can be used with chi's mux.\n\n### Core middlewares\n\n----------------------------------------------------------------------------------------------------\n| chi/middleware Handler | description                                                             |\n| :--------------------- | :---------------------------------------------------------------------- |\n| [AllowContentEncoding] | Enforces a whitelist of request Content-Encoding headers                |\n| [AllowContentType]     | Explicit whitelist of accepted request Content-Types                    |\n| [BasicAuth]            | Basic HTTP authentication                                               |\n| [Compress]             | Gzip compression for clients that accept compressed responses           |\n| [ContentCharset]       | Ensure charset for Content-Type request headers                         |\n| [CleanPath]            | Clean double slashes from request path                                  |\n| [GetHead]              | Automatically route undefined HEAD requests to GET handlers             |\n| [Heartbeat]            | Monitoring endpoint to check the servers pulse                          |\n| [Logger]               | Logs the start and end of each request with the elapsed processing time |\n| [NoCache]              | Sets response headers to prevent clients from caching                   |\n| [Profiler]             | Easily attach net/http/pprof to your routers                            |\n| [RealIP]               | Sets a http.Request's RemoteAddr to either X-Real-IP or X-Forwarded-For |\n| [Recoverer]            | Gracefully absorb panics and prints the stack trace                     |\n| [RequestID]            | Injects a request ID into the context of each request                   |\n| [RedirectSlashes]      | Redirect slashes on routing paths                                       |\n| [RouteHeaders]         | Route handling for request headers                                      |\n| [SetHeader]            | Short-hand middleware to set a response header key/value                |\n| [StripSlashes]         | Strip slashes on routing paths                                          |\n| [Sunset]               | Sunset set Deprecation/Sunset header to response                        |\n| [Throttle]             | Puts a ceiling on the number of concurrent requests                     |\n| [Timeout]              | Signals to the request context when the timeout deadline is reached     |\n| [URLFormat]            | Parse extension from url and put it on request context                  |\n| [WithValue]            | Short-hand middleware to set a key/value on the request context         |\n----------------------------------------------------------------------------------------------------\n\n[AllowContentEncoding]: https://pkg.go.dev/github.com/go-chi/chi/middleware#AllowContentEncoding\n[AllowContentType]: https://pkg.go.dev/github.com/go-chi/chi/middleware#AllowContentType\n[BasicAuth]: https://pkg.go.dev/github.com/go-chi/chi/middleware#BasicAuth\n[Compress]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Compress\n[ContentCharset]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ContentCharset\n[CleanPath]: https://pkg.go.dev/github.com/go-chi/chi/middleware#CleanPath\n[GetHead]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetHead\n[GetReqID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetReqID\n[Heartbeat]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Heartbeat\n[Logger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Logger\n[NoCache]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NoCache\n[Profiler]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Profiler\n[RealIP]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RealIP\n[Recoverer]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Recoverer\n[RedirectSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RedirectSlashes\n[RequestLogger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestLogger\n[RequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestID\n[RouteHeaders]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RouteHeaders\n[SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader\n[StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes\n[Sunset]: https://pkg.go.dev/github.com/go-chi/chi/v5/middleware#Sunset\n[Throttle]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Throttle\n[ThrottleBacklog]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleBacklog\n[ThrottleWithOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleWithOpts\n[Timeout]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Timeout\n[URLFormat]: https://pkg.go.dev/github.com/go-chi/chi/middleware#URLFormat\n[WithLogEntry]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WithLogEntry\n[WithValue]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WithValue\n[Compressor]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Compressor\n[DefaultLogFormatter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#DefaultLogFormatter\n[EncoderFunc]: https://pkg.go.dev/github.com/go-chi/chi/middleware#EncoderFunc\n[HeaderRoute]: https://pkg.go.dev/github.com/go-chi/chi/middleware#HeaderRoute\n[HeaderRouter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#HeaderRouter\n[LogEntry]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogEntry\n[LogFormatter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogFormatter\n[LoggerInterface]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LoggerInterface\n[ThrottleOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleOpts\n[WrapResponseWriter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WrapResponseWriter\n\n### Extra middlewares & packages\n\nPlease see https://github.com/go-chi for additional packages.\n\n--------------------------------------------------------------------------------------------------------------------\n| package                                            | description                                                 |\n|:---------------------------------------------------|:-------------------------------------------------------------\n| [cors](https://github.com/go-chi/cors)             | Cross-origin resource sharing (CORS)                        |\n| [docgen](https://github.com/go-chi/docgen)         | Print chi.Router routes at runtime                          |\n| [jwtauth](https://github.com/go-chi/jwtauth)       | JWT authentication                                          |\n| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing                           |\n| [httplog](https://github.com/go-chi/httplog)       | Small but powerful structured HTTP request logging          |\n| [httprate](https://github.com/go-chi/httprate)     | HTTP request rate limiter                                   |\n| [httptracer](https://github.com/go-chi/httptracer) | HTTP request performance tracing library                    |\n| [httpvcr](https://github.com/go-chi/httpvcr)       | Write deterministic tests for external sources              |\n| [stampede](https://github.com/go-chi/stampede)     | HTTP request coalescer                                      |\n--------------------------------------------------------------------------------------------------------------------\n\n\n## context?\n\n`context` is a tiny pkg that provides simple interface to signal context across call stacks\nand goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani)\nand is available in stdlib since go1.7.\n\nLearn more at https://blog.golang.org/context\n\nand..\n* Docs: https://golang.org/pkg/context\n* Source: https://github.com/golang/go/tree/master/src/context\n\n\n## Benchmarks\n\nThe benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark\n\nResults as of Nov 29, 2020 with Go 1.15.5 on Linux AMD 3950x\n\n```shell\nBenchmarkChi_Param          \t3075895\t        384 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_Param5         \t2116603\t        566 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_Param20        \t 964117\t       1227 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_ParamWrite     \t2863413\t        420 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_GithubStatic   \t3045488\t        395 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_GithubParam    \t2204115\t        540 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_GithubAll      \t  10000\t     113811 ns/op\t    81203 B/op    406 allocs/op\nBenchmarkChi_GPlusStatic    \t3337485\t        359 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_GPlusParam     \t2825853\t        423 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_GPlus2Params   \t2471697\t        483 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_GPlusAll       \t 194220\t       5950 ns/op\t     5200 B/op     26 allocs/op\nBenchmarkChi_ParseStatic    \t3365324\t        356 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_ParseParam     \t2976614\t        404 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_Parse2Params   \t2638084\t        439 ns/op\t      400 B/op      2 allocs/op\nBenchmarkChi_ParseAll       \t 109567\t      11295 ns/op\t    10400 B/op     52 allocs/op\nBenchmarkChi_StaticAll      \t  16846\t      71308 ns/op\t    62802 B/op    314 allocs/op\n```\n\nComparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc\n\nNOTE: the allocs in the benchmark above are from the calls to http.Request's\n`WithContext(context.Context)` method that clones the http.Request, sets the `Context()`\non the duplicated (alloc'd) request and returns it the new request object. This is just\nhow setting context on a request in Go works.\n\n\n## Credits\n\n* Carl Jackson for https://github.com/zenazn/goji\n  * Parts of chi's thinking comes from goji, and chi's middleware package\n    sources from [goji](https://github.com/zenazn/goji/tree/master/web/middleware).\n  * Please see goji's [LICENSE](https://github.com/zenazn/goji/blob/master/LICENSE) (MIT)\n* Armon Dadgar for https://github.com/armon/go-radix\n* Contributions: [@VojtechVitek](https://github.com/VojtechVitek)\n\nWe'll be more than happy to see [your contributions](./CONTRIBUTING.md)!\n\n\n## Beyond REST\n\nchi is just a http router that lets you decompose request handling into many smaller layers.\nMany companies use chi to write REST services for their public APIs. But, REST is just a convention\nfor managing state via HTTP, and there's a lot of other pieces required to write a complete client-server\nsystem or network of microservices.\n\nLooking beyond REST, I also recommend some newer works in the field:\n* [webrpc](https://github.com/webrpc/webrpc) - Web-focused RPC client+server framework with code-gen\n* [gRPC](https://github.com/grpc/grpc-go) - Google's RPC framework via protobufs\n* [graphql](https://github.com/99designs/gqlgen) - Declarative query language\n* [NATS](https://nats.io) - lightweight pub-sub\n\n\n## License\n\nCopyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka)\n\nLicensed under [MIT License](./LICENSE)\n\n[GoDoc]: https://pkg.go.dev/github.com/go-chi/chi/v5\n[GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg\n[Travis]: https://travis-ci.org/go-chi/chi\n[Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Reporting Security Issues\n\nWe appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.\n\nTo report a security issue, please use the GitHub Security Advisory [\"Report a Vulnerability\"](https://github.com/go-chi/chi/security/advisories/new) tab.\n"
  },
  {
    "path": "_examples/README.md",
    "content": "chi examples\n============\n\n* [custom-handler](https://github.com/go-chi/chi/blob/master/_examples/custom-handler/main.go) - Use a custom handler function signature\n* [custom-method](https://github.com/go-chi/chi/blob/master/_examples/custom-method/main.go) - Add a custom HTTP method\n* [fileserver](https://github.com/go-chi/chi/blob/master/_examples/fileserver/main.go) - Easily serve static files\n* [graceful](https://github.com/go-chi/chi/blob/master/_examples/graceful/main.go) - Graceful context signaling and server shutdown\n* [hello-world](https://github.com/go-chi/chi/blob/master/_examples/hello-world/main.go) - Hello World!\n* [limits](https://github.com/go-chi/chi/blob/master/_examples/limits/main.go) - Timeouts and Throttling\n* [logging](https://github.com/go-chi/chi/blob/master/_examples/logging/main.go) - Easy structured logging for any backend\n* [rest](https://github.com/go-chi/chi/blob/master/_examples/rest/main.go) - REST APIs made easy, productive and maintainable\n* [router-walk](https://github.com/go-chi/chi/blob/master/_examples/router-walk/main.go) - Print to stdout a router's routes\n* [todos-resource](https://github.com/go-chi/chi/blob/master/_examples/todos-resource/main.go) - Struct routers/handlers, an example of another code layout style\n* [versions](https://github.com/go-chi/chi/blob/master/_examples/versions/main.go) - Demo of `chi/render` subpkg\n* [pathvalue](https://github.com/go-chi/chi/blob/master/_examples/pathvalue/main.go) - Demonstrates `PathValue` usage for retrieving URL parameters\n\n\n## Usage\n\n1. `go get -v -d -u ./...` - fetch example deps\n2. `cd <example>/` ie. `cd rest/`\n3. `go run *.go` - note, example services run on port 3333\n4. Open another terminal and use curl to send some requests to your example service,\n   `curl -v http://localhost:3333/`\n5. Read <example>/main.go source to learn how service works and read comments for usage\n"
  },
  {
    "path": "_examples/custom-handler/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\ntype Handler func(w http.ResponseWriter, r *http.Request) error\n\nfunc (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tif err := h(w, r); err != nil {\n\t\t// handle returned error here.\n\t\tw.WriteHeader(503)\n\t\tw.Write([]byte(\"bad\"))\n\t}\n}\n\nfunc main() {\n\tr := chi.NewRouter()\n\tr.Method(\"GET\", \"/\", Handler(customHandler))\n\thttp.ListenAndServe(\":3333\", r)\n}\n\nfunc customHandler(w http.ResponseWriter, r *http.Request) error {\n\tq := r.URL.Query().Get(\"err\")\n\n\tif q != \"\" {\n\t\treturn errors.New(q)\n\t}\n\n\tw.Write([]byte(\"foo\"))\n\treturn nil\n}\n"
  },
  {
    "path": "_examples/custom-method/main.go",
    "content": "package main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc init() {\n\tchi.RegisterMethod(\"LINK\")\n\tchi.RegisterMethod(\"UNLINK\")\n\tchi.RegisterMethod(\"WOOHOO\")\n}\n\nfunc main() {\n\tr := chi.NewRouter()\n\tr.Use(middleware.RequestID)\n\tr.Use(middleware.Logger)\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hello world\"))\n\t})\n\tr.MethodFunc(\"LINK\", \"/link\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"custom link method\"))\n\t})\n\tr.MethodFunc(\"WOOHOO\", \"/woo\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"custom woohoo method\"))\n\t})\n\tr.HandleFunc(\"/everything\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"capturing all standard http methods, as well as LINK, UNLINK and WOOHOO\"))\n\t})\n\thttp.ListenAndServe(\":3333\", r)\n}\n"
  },
  {
    "path": "_examples/fileserver/data/notes.txt",
    "content": "Notessszzz\n"
  },
  {
    "path": "_examples/fileserver/main.go",
    "content": "// This example demonstrates how to serve static files from your filesystem.\n//\n// Boot the server:\n//\n//\t$ go run main.go\n//\n// Client requests:\n//\n//\t$ curl http://localhost:3333/files/\n//\t<pre>\n//\t<a href=\"notes.txt\">notes.txt</a>\n//\t</pre>\n//\n//\t$ curl http://localhost:3333/files/notes.txt\n//\tNotessszzz\npackage main\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\tr.Use(middleware.Logger)\n\n\t// Index handler\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hi\"))\n\t})\n\n\t// Create a route along /files that will serve contents from\n\t// the ./data/ folder.\n\tworkDir, _ := os.Getwd()\n\tfilesDir := http.Dir(filepath.Join(workDir, \"data\"))\n\tFileServer(r, \"/files\", filesDir)\n\n\thttp.ListenAndServe(\":3333\", r)\n}\n\n// FileServer conveniently sets up a http.FileServer handler to serve\n// static files from a http.FileSystem.\nfunc FileServer(r chi.Router, path string, root http.FileSystem) {\n\tif strings.ContainsAny(path, \"{}*\") {\n\t\tpanic(\"FileServer does not permit any URL parameters.\")\n\t}\n\n\tif path != \"/\" && path[len(path)-1] != '/' {\n\t\tr.Get(path, http.RedirectHandler(path+\"/\", 301).ServeHTTP)\n\t\tpath += \"/\"\n\t}\n\tpath += \"*\"\n\n\tr.Get(path, func(w http.ResponseWriter, r *http.Request) {\n\t\trctx := chi.RouteContext(r.Context())\n\t\tpathPrefix := strings.TrimSuffix(rctx.RoutePattern(), \"/*\")\n\t\tfs := http.StripPrefix(pathPrefix, http.FileServer(root))\n\t\tfs.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "_examples/graceful/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc main() {\n\t// The HTTP Server\n\tserver := &http.Server{Addr: \"0.0.0.0:3333\", Handler: service()}\n\n\t// Create context that listens for the interrupt signal\n\tctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)\n\tdefer stop()\n\n\t// Run server in the background\n\tgo func() {\n\t\tif err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}()\n\n\t// Listen for the interrupt signal\n\t<-ctx.Done()\n\n\t// Create shutdown context with 30-second timeout\n\tshutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer cancel()\n\n\t// Trigger graceful shutdown\n\tif err := server.Shutdown(shutdownCtx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc service() http.Handler {\n\tr := chi.NewRouter()\n\n\tr.Use(middleware.RequestID)\n\tr.Use(middleware.Logger)\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"sup\"))\n\t})\n\n\tr.Get(\"/slow\", func(w http.ResponseWriter, r *http.Request) {\n\t\t// Simulates some hard work.\n\t\t//\n\t\t// We want this handler to complete successfully during a shutdown signal,\n\t\t// so consider the work here as some background routine to fetch a long-running\n\t\t// search query to find as many results as possible, but, instead we cut it short\n\t\t// and respond with what we have so far. How a shutdown is handled is entirely\n\t\t// up to the developer, as some code blocks are preemptible, and others are not.\n\t\ttime.Sleep(5 * time.Second)\n\n\t\tw.Write([]byte(fmt.Sprintf(\"all done.\\n\")))\n\t})\n\n\treturn r\n}\n"
  },
  {
    "path": "_examples/hello-world/main.go",
    "content": "package main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\tr.Use(middleware.RequestID)\n\tr.Use(middleware.Logger)\n\tr.Use(middleware.Recoverer)\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hello world\"))\n\t})\n\n\thttp.ListenAndServe(\":3333\", r)\n}\n"
  },
  {
    "path": "_examples/limits/main.go",
    "content": "// This example demonstrates the use of Timeout, and Throttle middlewares.\n//\n// Timeout: cancel a request if processing takes longer than 2.5 seconds,\n// server will respond with a http.StatusGatewayTimeout.\n//\n// Throttle: limit the number of in-flight requests along a particular\n// routing path and backlog the others.\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\n\tr.Use(middleware.RequestID)\n\tr.Use(middleware.Logger)\n\tr.Use(middleware.Recoverer)\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"root.\"))\n\t})\n\n\tr.Get(\"/ping\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"pong\"))\n\t})\n\n\tr.Get(\"/panic\", func(w http.ResponseWriter, r *http.Request) {\n\t\tpanic(\"test\")\n\t})\n\n\t// Slow handlers/operations.\n\tr.Group(func(r chi.Router) {\n\t\t// Stop processing after 2.5 seconds.\n\t\tr.Use(middleware.Timeout(2500 * time.Millisecond))\n\n\t\tr.Get(\"/slow\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\trand.Seed(time.Now().Unix())\n\n\t\t\t// Processing will take 1-5 seconds.\n\t\t\tprocessTime := time.Duration(rand.Intn(4)+1) * time.Second\n\n\t\t\tselect {\n\t\t\tcase <-r.Context().Done():\n\t\t\t\treturn\n\n\t\t\tcase <-time.After(processTime):\n\t\t\t\t// The above channel simulates some hard work.\n\t\t\t}\n\n\t\t\tw.Write([]byte(fmt.Sprintf(\"Processed in %v seconds\\n\", processTime)))\n\t\t})\n\t})\n\n\t// Throttle very expensive handlers/operations.\n\tr.Group(func(r chi.Router) {\n\t\t// Stop processing after 30 seconds.\n\t\tr.Use(middleware.Timeout(30 * time.Second))\n\n\t\t// Only one request will be processed at a time.\n\t\tr.Use(middleware.Throttle(1))\n\n\t\tr.Get(\"/throttled\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tselect {\n\t\t\tcase <-r.Context().Done():\n\t\t\t\tswitch r.Context().Err() {\n\t\t\t\tcase context.DeadlineExceeded:\n\t\t\t\t\tw.WriteHeader(504)\n\t\t\t\t\tw.Write([]byte(\"Processing too slow\\n\"))\n\t\t\t\tdefault:\n\t\t\t\t\tw.Write([]byte(\"Canceled\\n\"))\n\t\t\t\t}\n\t\t\t\treturn\n\n\t\t\tcase <-time.After(5 * time.Second):\n\t\t\t\t// The above channel simulates some hard work.\n\t\t\t}\n\n\t\t\tw.Write([]byte(\"Processed\\n\"))\n\t\t})\n\t})\n\n\thttp.ListenAndServe(\":3333\", r)\n}\n"
  },
  {
    "path": "_examples/logging/main.go",
    "content": "package main\n\n// Please see https://github.com/go-chi/httplog for a complete package\n// and example for writing a structured logger on chi built on\n// the Go 1.21+ \"log/slog\" package.\n\nfunc main() {\n\t// See https://github.com/go-chi/httplog/blob/master/_example/main.go\n}\n"
  },
  {
    "path": "_examples/pathvalue/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\n\t// Registering a handler that retrieves a path parameter using PathValue\n\tr.Get(\"/users/{userID}\", pathValueHandler)\n\n\thttp.ListenAndServe(\":3333\", r)\n}\n\n// pathValueHandler retrieves a URL parameter using PathValue and writes it to the response.\nfunc pathValueHandler(w http.ResponseWriter, r *http.Request) {\n\tuserID := r.PathValue(\"userID\")\n\n\t// Respond with the extracted userID\n\tw.Write([]byte(fmt.Sprintf(\"User ID: %s\", userID)))\n}\n"
  },
  {
    "path": "_examples/rest/go.mod",
    "content": "module rest-example\n\ngo 1.16\n\nrequire (\n\tgithub.com/go-chi/chi/v5 v5.0.1\n\tgithub.com/go-chi/docgen v1.2.0\n\tgithub.com/go-chi/render v1.0.1\n)\n"
  },
  {
    "path": "_examples/rest/go.sum",
    "content": "github.com/go-chi/chi/v5 v5.0.1 h1:ALxjCrTf1aflOlkhMnCUP86MubbWFrzB3gkRPReLpTo=\ngithub.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=\ngithub.com/go-chi/docgen v1.2.0 h1:da0Nq2PKU9W9pSOTUfVrKI1vIgTGpauo9cfh4Iwivek=\ngithub.com/go-chi/docgen v1.2.0/go.mod h1:G9W0G551cs2BFMSn/cnGwX+JBHEloAgo17MBhyrnhPI=\ngithub.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=\ngithub.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\n"
  },
  {
    "path": "_examples/rest/main.go",
    "content": "// This example demonstrates a HTTP REST web service with some fixture data.\n// Follow along the example and patterns.\n//\n// Also check routes.json for the generated docs from passing the -routes flag,\n// to run yourself do: `go run . -routes`\n//\n// Boot the server:\n//\n//\t$ go run main.go\n//\n// Client requests:\n//\n//\t$ curl http://localhost:3333/\n//\troot.\n//\n//\t$ curl http://localhost:3333/articles\n//\t[{\"id\":\"1\",\"title\":\"Hi\"},{\"id\":\"2\",\"title\":\"sup\"}]\n//\n//\t$ curl http://localhost:3333/articles/1\n//\t{\"id\":\"1\",\"title\":\"Hi\"}\n//\n//\t$ curl -X DELETE http://localhost:3333/articles/1\n//\t{\"id\":\"1\",\"title\":\"Hi\"}\n//\n//\t$ curl http://localhost:3333/articles/1\n//\t\"Not Found\"\n//\n//\t$ curl -X POST -d '{\"id\":\"will-be-omitted\",\"title\":\"awesomeness\"}' http://localhost:3333/articles\n//\t{\"id\":\"97\",\"title\":\"awesomeness\"}\n//\n//\t$ curl http://localhost:3333/articles/97\n//\t{\"id\":\"97\",\"title\":\"awesomeness\"}\n//\n//\t$ curl http://localhost:3333/articles\n//\t[{\"id\":\"2\",\"title\":\"sup\"},{\"id\":\"97\",\"title\":\"awesomeness\"}]\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n\t\"github.com/go-chi/docgen\"\n\t\"github.com/go-chi/render\"\n)\n\nvar routes = flag.Bool(\"routes\", false, \"Generate router documentation\")\n\nfunc main() {\n\tflag.Parse()\n\n\tr := chi.NewRouter()\n\n\tr.Use(middleware.RequestID)\n\tr.Use(middleware.Logger)\n\tr.Use(middleware.Recoverer)\n\tr.Use(middleware.URLFormat)\n\tr.Use(render.SetContentType(render.ContentTypeJSON))\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"root.\"))\n\t})\n\n\tr.Get(\"/ping\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"pong\"))\n\t})\n\n\tr.Get(\"/panic\", func(w http.ResponseWriter, r *http.Request) {\n\t\tpanic(\"test\")\n\t})\n\n\t// RESTy routes for \"articles\" resource\n\tr.Route(\"/articles\", func(r chi.Router) {\n\t\tr.With(paginate).Get(\"/\", ListArticles)\n\t\tr.Post(\"/\", CreateArticle)       // POST /articles\n\t\tr.Get(\"/search\", SearchArticles) // GET /articles/search\n\n\t\tr.Route(\"/{articleID}\", func(r chi.Router) {\n\t\t\tr.Use(ArticleCtx)            // Load the *Article on the request context\n\t\t\tr.Get(\"/\", GetArticle)       // GET /articles/123\n\t\t\tr.Put(\"/\", UpdateArticle)    // PUT /articles/123\n\t\t\tr.Delete(\"/\", DeleteArticle) // DELETE /articles/123\n\t\t})\n\n\t\t// GET /articles/whats-up\n\t\tr.With(ArticleCtx).Get(\"/{articleSlug:[a-z-]+}\", GetArticle)\n\t})\n\n\t// Mount the admin sub-router, which btw is the same as:\n\t// r.Route(\"/admin\", func(r chi.Router) { admin routes here })\n\tr.Mount(\"/admin\", adminRouter())\n\n\t// Passing -routes to the program will generate docs for the above\n\t// router definition. See the `routes.json` file in this folder for\n\t// the output.\n\tif *routes {\n\t\t// fmt.Println(docgen.JSONRoutesDoc(r))\n\t\tfmt.Println(docgen.MarkdownRoutesDoc(r, docgen.MarkdownOpts{\n\t\t\tProjectPath: \"github.com/go-chi/chi/v5\",\n\t\t\tIntro:       \"Welcome to the chi/_examples/rest generated docs.\",\n\t\t}))\n\t\treturn\n\t}\n\n\thttp.ListenAndServe(\":3333\", r)\n}\n\nfunc ListArticles(w http.ResponseWriter, r *http.Request) {\n\tif err := render.RenderList(w, r, NewArticleListResponse(articles)); err != nil {\n\t\trender.Render(w, r, ErrRender(err))\n\t\treturn\n\t}\n}\n\n// ArticleCtx middleware is used to load an Article object from\n// the URL parameters passed through as the request. In case\n// the Article could not be found, we stop here and return a 404.\nfunc ArticleCtx(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tvar article *Article\n\t\tvar err error\n\n\t\tif articleID := chi.URLParam(r, \"articleID\"); articleID != \"\" {\n\t\t\tarticle, err = dbGetArticle(articleID)\n\t\t} else if articleSlug := chi.URLParam(r, \"articleSlug\"); articleSlug != \"\" {\n\t\t\tarticle, err = dbGetArticleBySlug(articleSlug)\n\t\t} else {\n\t\t\trender.Render(w, r, ErrNotFound)\n\t\t\treturn\n\t\t}\n\t\tif err != nil {\n\t\t\trender.Render(w, r, ErrNotFound)\n\t\t\treturn\n\t\t}\n\n\t\tctx := context.WithValue(r.Context(), \"article\", article)\n\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t})\n}\n\n// SearchArticles searches the Articles data for a matching article.\n// It's just a stub, but you get the idea.\nfunc SearchArticles(w http.ResponseWriter, r *http.Request) {\n\trender.RenderList(w, r, NewArticleListResponse(articles))\n}\n\n// CreateArticle persists the posted Article and returns it\n// back to the client as an acknowledgement.\nfunc CreateArticle(w http.ResponseWriter, r *http.Request) {\n\tdata := &ArticleRequest{}\n\tif err := render.Bind(r, data); err != nil {\n\t\trender.Render(w, r, ErrInvalidRequest(err))\n\t\treturn\n\t}\n\n\tarticle := data.Article\n\tdbNewArticle(article)\n\n\trender.Status(r, http.StatusCreated)\n\trender.Render(w, r, NewArticleResponse(article))\n}\n\n// GetArticle returns the specific Article. You'll notice it just\n// fetches the Article right off the context, as its understood that\n// if we made it this far, the Article must be on the context. In case\n// its not due to a bug, then it will panic, and our Recoverer will save us.\nfunc GetArticle(w http.ResponseWriter, r *http.Request) {\n\t// Assume if we've reach this far, we can access the article\n\t// context because this handler is a child of the ArticleCtx\n\t// middleware. The worst case, the recoverer middleware will save us.\n\tarticle := r.Context().Value(\"article\").(*Article)\n\n\tif err := render.Render(w, r, NewArticleResponse(article)); err != nil {\n\t\trender.Render(w, r, ErrRender(err))\n\t\treturn\n\t}\n}\n\n// UpdateArticle updates an existing Article in our persistent store.\nfunc UpdateArticle(w http.ResponseWriter, r *http.Request) {\n\tarticle := r.Context().Value(\"article\").(*Article)\n\n\tdata := &ArticleRequest{Article: article}\n\tif err := render.Bind(r, data); err != nil {\n\t\trender.Render(w, r, ErrInvalidRequest(err))\n\t\treturn\n\t}\n\tarticle = data.Article\n\tdbUpdateArticle(article.ID, article)\n\n\trender.Render(w, r, NewArticleResponse(article))\n}\n\n// DeleteArticle removes an existing Article from our persistent store.\nfunc DeleteArticle(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\n\t// Assume if we've reach this far, we can access the article\n\t// context because this handler is a child of the ArticleCtx\n\t// middleware. The worst case, the recoverer middleware will save us.\n\tarticle := r.Context().Value(\"article\").(*Article)\n\n\tarticle, err = dbRemoveArticle(article.ID)\n\tif err != nil {\n\t\trender.Render(w, r, ErrInvalidRequest(err))\n\t\treturn\n\t}\n\n\trender.Render(w, r, NewArticleResponse(article))\n}\n\n// A completely separate router for administrator routes\nfunc adminRouter() chi.Router {\n\tr := chi.NewRouter()\n\tr.Use(AdminOnly)\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"admin: index\"))\n\t})\n\tr.Get(\"/accounts\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"admin: list accounts..\"))\n\t})\n\tr.Get(\"/users/{userId}\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(fmt.Sprintf(\"admin: view user id %v\", chi.URLParam(r, \"userId\"))))\n\t})\n\treturn r\n}\n\n// AdminOnly middleware restricts access to just administrators.\nfunc AdminOnly(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tisAdmin, ok := r.Context().Value(\"acl.admin\").(bool)\n\t\tif !ok || !isAdmin {\n\t\t\thttp.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)\n\t\t\treturn\n\t\t}\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\n// paginate is a stub, but very possible to implement middleware logic\n// to handle the request params for handling a paginated request.\nfunc paginate(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// just a stub.. some ideas are to look at URL query params for something like\n\t\t// the page number, or the limit, and send a query cursor down the chain\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\n// This is entirely optional, but I wanted to demonstrate how you could easily\n// add your own logic to the render.Respond method.\nfunc init() {\n\trender.Respond = func(w http.ResponseWriter, r *http.Request, v interface{}) {\n\t\tif err, ok := v.(error); ok {\n\n\t\t\t// We set a default error status response code if one hasn't been set.\n\t\t\tif _, ok := r.Context().Value(render.StatusCtxKey).(int); !ok {\n\t\t\t\tw.WriteHeader(400)\n\t\t\t}\n\n\t\t\t// We log the error\n\t\t\tfmt.Printf(\"Logging err: %s\\n\", err.Error())\n\n\t\t\t// We change the response to not reveal the actual error message,\n\t\t\t// instead we can transform the message something more friendly or mapped\n\t\t\t// to some code / language, etc.\n\t\t\trender.DefaultResponder(w, r, render.M{\"status\": \"error\"})\n\t\t\treturn\n\t\t}\n\n\t\trender.DefaultResponder(w, r, v)\n\t}\n}\n\n//--\n// Request and Response payloads for the REST api.\n//\n// The payloads embed the data model objects an\n//\n// In a real-world project, it would make sense to put these payloads\n// in another file, or another sub-package.\n//--\n\ntype UserPayload struct {\n\t*User\n\tRole string `json:\"role\"`\n}\n\nfunc NewUserPayloadResponse(user *User) *UserPayload {\n\treturn &UserPayload{User: user}\n}\n\n// Bind on UserPayload will run after the unmarshalling is complete, its\n// a good time to focus some post-processing after a decoding.\nfunc (u *UserPayload) Bind(r *http.Request) error {\n\treturn nil\n}\n\nfunc (u *UserPayload) Render(w http.ResponseWriter, r *http.Request) error {\n\tu.Role = \"collaborator\"\n\treturn nil\n}\n\n// ArticleRequest is the request payload for Article data model.\n//\n// NOTE: It's good practice to have well defined request and response payloads\n// so you can manage the specific inputs and outputs for clients, and also gives\n// you the opportunity to transform data on input or output, for example\n// on request, we'd like to protect certain fields and on output perhaps\n// we'd like to include a computed field based on other values that aren't\n// in the data model. Also, check out this awesome blog post on struct composition:\n// http://attilaolah.eu/2014/09/10/json-and-struct-composition-in-go/\ntype ArticleRequest struct {\n\t*Article\n\n\tUser *UserPayload `json:\"user,omitempty\"`\n\n\tProtectedID string `json:\"id\"` // override 'id' json to have more control\n}\n\nfunc (a *ArticleRequest) Bind(r *http.Request) error {\n\t// a.Article is nil if no Article fields are sent in the request. Return an\n\t// error to avoid a nil pointer dereference.\n\tif a.Article == nil {\n\t\treturn errors.New(\"missing required Article fields.\")\n\t}\n\n\t// a.User is nil if no Userpayload fields are sent in the request. In this app\n\t// this won't cause a panic, but checks in this Bind method may be required if\n\t// a.User or further nested fields like a.User.Name are accessed elsewhere.\n\n\t// just a post-process after a decode..\n\ta.ProtectedID = \"\"                                 // unset the protected ID\n\ta.Article.Title = strings.ToLower(a.Article.Title) // as an example, we down-case\n\treturn nil\n}\n\n// ArticleResponse is the response payload for the Article data model.\n// See NOTE above in ArticleRequest as well.\n//\n// In the ArticleResponse object, first a Render() is called on itself,\n// then the next field, and so on, all the way down the tree.\n// Render is called in top-down order, like a http handler middleware chain.\ntype ArticleResponse struct {\n\t*Article\n\n\tUser *UserPayload `json:\"user,omitempty\"`\n\n\t// We add an additional field to the response here.. such as this\n\t// elapsed computed property\n\tElapsed int64 `json:\"elapsed\"`\n}\n\nfunc NewArticleResponse(article *Article) *ArticleResponse {\n\tresp := &ArticleResponse{Article: article}\n\n\tif resp.User == nil {\n\t\tif user, _ := dbGetUser(resp.UserID); user != nil {\n\t\t\tresp.User = NewUserPayloadResponse(user)\n\t\t}\n\t}\n\n\treturn resp\n}\n\nfunc (rd *ArticleResponse) Render(w http.ResponseWriter, r *http.Request) error {\n\t// Pre-processing before a response is marshalled and sent across the wire\n\trd.Elapsed = 10\n\treturn nil\n}\n\nfunc NewArticleListResponse(articles []*Article) []render.Renderer {\n\tlist := []render.Renderer{}\n\tfor _, article := range articles {\n\t\tlist = append(list, NewArticleResponse(article))\n\t}\n\treturn list\n}\n\n// NOTE: as a thought, the request and response payloads for an Article could be the\n// same payload type, perhaps will do an example with it as well.\n// type ArticlePayload struct {\n//   *Article\n// }\n\n//--\n// Error response payloads & renderers\n//--\n\n// ErrResponse renderer type for handling all sorts of errors.\n//\n// In the best case scenario, the excellent github.com/pkg/errors package\n// helps reveal information on the error, setting it on Err, and in the Render()\n// method, using it to set the application-specific error code in AppCode.\ntype ErrResponse struct {\n\tErr            error `json:\"-\"` // low-level runtime error\n\tHTTPStatusCode int   `json:\"-\"` // http response status code\n\n\tStatusText string `json:\"status\"`          // user-level status message\n\tAppCode    int64  `json:\"code,omitempty\"`  // application-specific error code\n\tErrorText  string `json:\"error,omitempty\"` // application-level error message, for debugging\n}\n\nfunc (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error {\n\trender.Status(r, e.HTTPStatusCode)\n\treturn nil\n}\n\nfunc ErrInvalidRequest(err error) render.Renderer {\n\treturn &ErrResponse{\n\t\tErr:            err,\n\t\tHTTPStatusCode: 400,\n\t\tStatusText:     \"Invalid request.\",\n\t\tErrorText:      err.Error(),\n\t}\n}\n\nfunc ErrRender(err error) render.Renderer {\n\treturn &ErrResponse{\n\t\tErr:            err,\n\t\tHTTPStatusCode: 422,\n\t\tStatusText:     \"Error rendering response.\",\n\t\tErrorText:      err.Error(),\n\t}\n}\n\nvar ErrNotFound = &ErrResponse{HTTPStatusCode: 404, StatusText: \"Resource not found.\"}\n\n//--\n// Data model objects and persistence mocks:\n//--\n\n// User data model\ntype User struct {\n\tID   int64  `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// Article data model. I suggest looking at https://upper.io for an easy\n// and powerful data persistence adapter.\ntype Article struct {\n\tID     string `json:\"id\"`\n\tUserID int64  `json:\"user_id\"` // the author\n\tTitle  string `json:\"title\"`\n\tSlug   string `json:\"slug\"`\n}\n\n// Article fixture data\nvar articles = []*Article{\n\t{ID: \"1\", UserID: 100, Title: \"Hi\", Slug: \"hi\"},\n\t{ID: \"2\", UserID: 200, Title: \"sup\", Slug: \"sup\"},\n\t{ID: \"3\", UserID: 300, Title: \"alo\", Slug: \"alo\"},\n\t{ID: \"4\", UserID: 400, Title: \"bonjour\", Slug: \"bonjour\"},\n\t{ID: \"5\", UserID: 500, Title: \"whats up\", Slug: \"whats-up\"},\n}\n\n// User fixture data\nvar users = []*User{\n\t{ID: 100, Name: \"Peter\"},\n\t{ID: 200, Name: \"Julia\"},\n}\n\nfunc dbNewArticle(article *Article) (string, error) {\n\tarticle.ID = fmt.Sprintf(\"%d\", rand.Intn(100)+10)\n\tarticles = append(articles, article)\n\treturn article.ID, nil\n}\n\nfunc dbGetArticle(id string) (*Article, error) {\n\tfor _, a := range articles {\n\t\tif a.ID == id {\n\t\t\treturn a, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"article not found.\")\n}\n\nfunc dbGetArticleBySlug(slug string) (*Article, error) {\n\tfor _, a := range articles {\n\t\tif a.Slug == slug {\n\t\t\treturn a, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"article not found.\")\n}\n\nfunc dbUpdateArticle(id string, article *Article) (*Article, error) {\n\tfor i, a := range articles {\n\t\tif a.ID == id {\n\t\t\tarticles[i] = article\n\t\t\treturn article, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"article not found.\")\n}\n\nfunc dbRemoveArticle(id string) (*Article, error) {\n\tfor i, a := range articles {\n\t\tif a.ID == id {\n\t\t\tarticles = append((articles)[:i], (articles)[i+1:]...)\n\t\t\treturn a, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"article not found.\")\n}\n\nfunc dbGetUser(id int64) (*User, error) {\n\tfor _, u := range users {\n\t\tif u.ID == id {\n\t\t\treturn u, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"user not found.\")\n}\n"
  },
  {
    "path": "_examples/rest/routes.json",
    "content": "{\n  \"router\": {\n    \"middlewares\": [\n      {\n        \"pkg\": \"github.com/go-chi/chi/v5/middleware\",\n        \"func\": \"RequestID\",\n        \"comment\": \"RequestID is a middleware that injects a request ID into the context of each\\nrequest. A request ID is a string of the form \\\"host.example.com/random-0001\\\",\\nwhere \\\"random\\\" is a base62 random string that uniquely identifies this go\\nprocess, and where the last number is an atomically incremented request\\ncounter.\\n\",\n        \"file\": \"github.com/go-chi/chi/middleware/request_id.go\",\n        \"line\": 63\n      },\n      {\n        \"pkg\": \"github.com/go-chi/chi/v5/middleware\",\n        \"func\": \"Logger\",\n        \"comment\": \"Logger is a middleware that logs the start and end of each request, along\\nwith some useful data about what was requested, what the response status was,\\nand how long it took to return. When standard output is a TTY, Logger will\\nprint in color, otherwise it will print in black and white. Logger prints a\\nrequest ID if one is provided.\\n\\nAlternatively, look at https://github.com/pressly/lg and the `lg.RequestLogger`\\nmiddleware pkg.\\n\",\n        \"file\": \"github.com/go-chi/chi/middleware/logger.go\",\n        \"line\": 26\n      },\n      {\n        \"pkg\": \"github.com/go-chi/chi/v5/middleware\",\n        \"func\": \"Recoverer\",\n        \"comment\": \"Recoverer is a middleware that recovers from panics, logs the panic (and a\\nbacktrace), and returns a HTTP 500 (Internal Server Error) status if\\npossible. Recoverer prints a request ID if one is provided.\\n\\nAlternatively, look at https://github.com/pressly/lg middleware pkgs.\\n\",\n        \"file\": \"github.com/go-chi/chi/middleware/recoverer.go\",\n        \"line\": 18\n      },\n      {\n        \"pkg\": \"github.com/go-chi/chi/v5/middleware\",\n        \"func\": \"URLFormat\",\n        \"comment\": \"URLFormat is a middleware that parses the url extension from a request path and stores it\\non the context as a string under the key `middleware.URLFormatCtxKey`. The middleware will\\ntrim the suffix from the routing path and continue routing.\\n\\nRouters should not include a url parameter for the suffix when using this middleware.\\n\\nSample usage.. for url paths: `/articles/1`, `/articles/1.json` and `/articles/1.xml`\\n\\n func routes() http.Handler {\\n   r := chi.NewRouter()\\n   r.Use(middleware.URLFormat)\\n\\n   r.Get(\\\"/articles/{id}\\\", ListArticles)\\n\\n   return r\\n }\\n\\n func ListArticles(w http.ResponseWriter, r *http.Request) {\\n\\t  urlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string)\\n\\n\\t  switch urlFormat {\\n\\t  case \\\"json\\\":\\n\\t  \\trender.JSON(w, r, articles)\\n\\t  case \\\"xml:\\\"\\n\\t  \\trender.XML(w, r, articles)\\n\\t  default:\\n\\t  \\trender.JSON(w, r, articles)\\n\\t  }\\n}\\n\",\n        \"file\": \"github.com/go-chi/chi/middleware/url_format.go\",\n        \"line\": 45\n      },\n      {\n        \"pkg\": \"github.com/go-chi/render\",\n        \"func\": \"SetContentType.func1\",\n        \"comment\": \"\",\n        \"file\": \"github.com/go-chi/render/content_type.go\",\n        \"line\": 49,\n        \"anonymous\": true\n      }\n    ],\n    \"routes\": {\n      \"/\": {\n        \"handlers\": {\n          \"GET\": {\n            \"middlewares\": [],\n            \"method\": \"GET\",\n            \"pkg\": \"\",\n            \"func\": \"main.main.func1\",\n            \"comment\": \"\",\n            \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n            \"line\": 69,\n            \"anonymous\": true\n          }\n        }\n      },\n      \"/admin/*\": {\n        \"router\": {\n          \"middlewares\": [\n            {\n              \"pkg\": \"\",\n              \"func\": \"main.AdminOnly\",\n              \"comment\": \"AdminOnly middleware restricts access to just administrators.\\n\",\n              \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n              \"line\": 238\n            }\n          ],\n          \"routes\": {\n            \"/\": {\n              \"handlers\": {\n                \"GET\": {\n                  \"middlewares\": [],\n                  \"method\": \"GET\",\n                  \"pkg\": \"\",\n                  \"func\": \"main.adminRouter.func1\",\n                  \"comment\": \"\",\n                  \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                  \"line\": 225,\n                  \"anonymous\": true\n                }\n              }\n            },\n            \"/accounts\": {\n              \"handlers\": {\n                \"GET\": {\n                  \"middlewares\": [],\n                  \"method\": \"GET\",\n                  \"pkg\": \"\",\n                  \"func\": \"main.adminRouter.func2\",\n                  \"comment\": \"\",\n                  \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                  \"line\": 228,\n                  \"anonymous\": true\n                }\n              }\n            },\n            \"/users/{userId}\": {\n              \"handlers\": {\n                \"GET\": {\n                  \"middlewares\": [],\n                  \"method\": \"GET\",\n                  \"pkg\": \"\",\n                  \"func\": \"main.adminRouter.func3\",\n                  \"comment\": \"\",\n                  \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                  \"line\": 231,\n                  \"anonymous\": true\n                }\n              }\n            }\n          }\n        }\n      },\n      \"/articles/*\": {\n        \"router\": {\n          \"middlewares\": [],\n          \"routes\": {\n            \"/\": {\n              \"handlers\": {\n                \"GET\": {\n                  \"middlewares\": [\n                    {\n                      \"pkg\": \"\",\n                      \"func\": \"main.paginate\",\n                      \"comment\": \"paginate is a stub, but very possible to implement middleware logic\\nto handle the request params for handling a paginated request.\\n\",\n                      \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                      \"line\": 251\n                    }\n                  ],\n                  \"method\": \"GET\",\n                  \"pkg\": \"\",\n                  \"func\": \"main.ListArticles\",\n                  \"comment\": \"\",\n                  \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                  \"line\": 117\n                },\n                \"POST\": {\n                  \"middlewares\": [],\n                  \"method\": \"POST\",\n                  \"pkg\": \"\",\n                  \"func\": \"main.CreateArticle\",\n                  \"comment\": \"CreateArticle persists the posted Article and returns it\\nback to the client as an acknowledgement.\\n\",\n                  \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                  \"line\": 158\n                }\n              }\n            },\n            \"/search\": {\n              \"handlers\": {\n                \"GET\": {\n                  \"middlewares\": [],\n                  \"method\": \"GET\",\n                  \"pkg\": \"\",\n                  \"func\": \"main.SearchArticles\",\n                  \"comment\": \"SearchArticles searches the Articles data for a matching article.\\nIt's just a stub, but you get the idea.\\n\",\n                  \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                  \"line\": 152\n                }\n              }\n            },\n            \"/{articleID}/*\": {\n              \"router\": {\n                \"middlewares\": [\n                  {\n                    \"pkg\": \"\",\n                    \"func\": \"main.ArticleCtx\",\n                    \"comment\": \"ArticleCtx middleware is used to load an Article object from\\nthe URL parameters passed through as the request. In case\\nthe Article could not be found, we stop here and return a 404.\\n\",\n                    \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                    \"line\": 127\n                  }\n                ],\n                \"routes\": {\n                  \"/\": {\n                    \"handlers\": {\n                      \"DELETE\": {\n                        \"middlewares\": [],\n                        \"method\": \"DELETE\",\n                        \"pkg\": \"\",\n                        \"func\": \"main.DeleteArticle\",\n                        \"comment\": \"DeleteArticle removes an existing Article from our persistent store.\\n\",\n                        \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                        \"line\": 204\n                      },\n                      \"GET\": {\n                        \"middlewares\": [],\n                        \"method\": \"GET\",\n                        \"pkg\": \"\",\n                        \"func\": \"main.GetArticle\",\n                        \"comment\": \"GetArticle returns the specific Article. You'll notice it just\\nfetches the Article right off the context, as its understood that\\nif we made it this far, the Article must be on the context. In case\\nits not due to a bug, then it will panic, and our Recoverer will save us.\\n\",\n                        \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                        \"line\": 176\n                      },\n                      \"PUT\": {\n                        \"middlewares\": [],\n                        \"method\": \"PUT\",\n                        \"pkg\": \"\",\n                        \"func\": \"main.UpdateArticle\",\n                        \"comment\": \"UpdateArticle updates an existing Article in our persistent store.\\n\",\n                        \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                        \"line\": 189\n                      }\n                    }\n                  }\n                }\n              }\n            },\n            \"/{articleSlug:[a-z-]+}\": {\n              \"handlers\": {\n                \"GET\": {\n                  \"middlewares\": [\n                    {\n                      \"pkg\": \"\",\n                      \"func\": \"main.ArticleCtx\",\n                      \"comment\": \"ArticleCtx middleware is used to load an Article object from\\nthe URL parameters passed through as the request. In case\\nthe Article could not be found, we stop here and return a 404.\\n\",\n                      \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                      \"line\": 127\n                    }\n                  ],\n                  \"method\": \"GET\",\n                  \"pkg\": \"\",\n                  \"func\": \"main.GetArticle\",\n                  \"comment\": \"GetArticle returns the specific Article. You'll notice it just\\nfetches the Article right off the context, as its understood that\\nif we made it this far, the Article must be on the context. In case\\nits not due to a bug, then it will panic, and our Recoverer will save us.\\n\",\n                  \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n                  \"line\": 176\n                }\n              }\n            }\n          }\n        }\n      },\n      \"/panic\": {\n        \"handlers\": {\n          \"GET\": {\n            \"middlewares\": [],\n            \"method\": \"GET\",\n            \"pkg\": \"\",\n            \"func\": \"main.main.func3\",\n            \"comment\": \"\",\n            \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n            \"line\": 77,\n            \"anonymous\": true\n          }\n        }\n      },\n      \"/ping\": {\n        \"handlers\": {\n          \"GET\": {\n            \"middlewares\": [],\n            \"method\": \"GET\",\n            \"pkg\": \"\",\n            \"func\": \"main.main.func2\",\n            \"comment\": \"\",\n            \"file\": \"github.com/go-chi/chi/_examples/rest/main.go\",\n            \"line\": 73,\n            \"anonymous\": true\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "_examples/rest/routes.md",
    "content": "# github.com/go-chi/chi\n\nWelcome to the chi/_examples/rest generated docs.\n\n## Routes\n\n<details>\n<summary>`/`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/**\n\t- _GET_\n\t\t- [main.main.func1](/_examples/rest/main.go#L69)\n\n</details>\n<details>\n<summary>`/admin/*`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/admin/***\n\t- [main.AdminOnly](/_examples/rest/main.go#L238)\n\t- **/**\n\t\t- _GET_\n\t\t\t- [main.adminRouter.func1](/_examples/rest/main.go#L225)\n\n</details>\n<details>\n<summary>`/admin/*/accounts`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/admin/***\n\t- [main.AdminOnly](/_examples/rest/main.go#L238)\n\t- **/accounts**\n\t\t- _GET_\n\t\t\t- [main.adminRouter.func2](/_examples/rest/main.go#L228)\n\n</details>\n<details>\n<summary>`/admin/*/users/{userId}`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/admin/***\n\t- [main.AdminOnly](/_examples/rest/main.go#L238)\n\t- **/users/{userId}**\n\t\t- _GET_\n\t\t\t- [main.adminRouter.func3](/_examples/rest/main.go#L231)\n\n</details>\n<details>\n<summary>`/articles/*`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/articles/***\n\t- **/**\n\t\t- _GET_\n\t\t\t- [main.paginate](/_examples/rest/main.go#L251)\n\t\t\t- [main.ListArticles](/_examples/rest/main.go#L117)\n\t\t- _POST_\n\t\t\t- [main.CreateArticle](/_examples/rest/main.go#L158)\n\n</details>\n<details>\n<summary>`/articles/*/search`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/articles/***\n\t- **/search**\n\t\t- _GET_\n\t\t\t- [main.SearchArticles](/_examples/rest/main.go#L152)\n\n</details>\n<details>\n<summary>`/articles/*/{articleID}/*`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/articles/***\n\t- **/{articleID}/***\n\t\t- [main.ArticleCtx](/_examples/rest/main.go#L127)\n\t\t- **/**\n\t\t\t- _DELETE_\n\t\t\t\t- [main.DeleteArticle](/_examples/rest/main.go#L204)\n\t\t\t- _GET_\n\t\t\t\t- [main.GetArticle](/_examples/rest/main.go#L176)\n\t\t\t- _PUT_\n\t\t\t\t- [main.UpdateArticle](/_examples/rest/main.go#L189)\n\n</details>\n<details>\n<summary>`/articles/*/{articleSlug:[a-z-]+}`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/articles/***\n\t- **/{articleSlug:[a-z-]+}**\n\t\t- _GET_\n\t\t\t- [main.ArticleCtx](/_examples/rest/main.go#L127)\n\t\t\t- [main.GetArticle](/_examples/rest/main.go#L176)\n\n</details>\n<details>\n<summary>`/panic`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/panic**\n\t- _GET_\n\t\t- [main.main.func3](/_examples/rest/main.go#L77)\n\n</details>\n<details>\n<summary>`/ping`</summary>\n\n- [RequestID](/middleware/request_id.go#L63)\n- [Logger](/middleware/logger.go#L26)\n- [Recoverer](/middleware/recoverer.go#L18)\n- [URLFormat](/middleware/url_format.go#L45)\n- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)\n- **/ping**\n\t- _GET_\n\t\t- [main.main.func2](/_examples/rest/main.go#L73)\n\n</details>\n\nTotal # of routes: 10\n\n"
  },
  {
    "path": "_examples/router-walk/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"root.\"))\n\t})\n\n\tr.Route(\"/road\", func(r chi.Router) {\n\t\tr.Get(\"/left\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"left road\"))\n\t\t})\n\t\tr.Post(\"/right\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"right road\"))\n\t\t})\n\t})\n\n\tr.Put(\"/ping\", Ping)\n\n\twalkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {\n\t\troute = strings.ReplaceAll(route, \"/*/\", \"/\")\n\t\tfmt.Printf(\"%s %s\\n\", method, route)\n\t\treturn nil\n\t}\n\n\tif err := chi.Walk(r, walkFunc); err != nil {\n\t\tfmt.Printf(\"Logging err: %s\\n\", err.Error())\n\t}\n}\n\n// Ping returns pong\nfunc Ping(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"pong\"))\n}\n"
  },
  {
    "path": "_examples/todos-resource/main.go",
    "content": "// This example demonstrates a project structure that defines a subrouter and its\n// handlers on a struct, and mounting them as subrouters to a parent router.\n// See also _examples/rest for an in-depth example of a REST service, and apply\n// those same patterns to this structure.\npackage main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\n\tr.Use(middleware.RequestID)\n\tr.Use(middleware.RealIP)\n\tr.Use(middleware.Logger)\n\tr.Use(middleware.Recoverer)\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\".\"))\n\t})\n\n\tr.Mount(\"/users\", usersResource{}.Routes())\n\tr.Mount(\"/todos\", todosResource{}.Routes())\n\n\thttp.ListenAndServe(\":3333\", r)\n}\n"
  },
  {
    "path": "_examples/todos-resource/todos.go",
    "content": "package main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\ntype todosResource struct{}\n\n// Routes creates a REST router for the todos resource\nfunc (rs todosResource) Routes() chi.Router {\n\tr := chi.NewRouter()\n\t// r.Use() // some middleware..\n\n\tr.Get(\"/\", rs.List)    // GET /todos - read a list of todos\n\tr.Post(\"/\", rs.Create) // POST /todos - create a new todo and persist it\n\tr.Put(\"/\", rs.Delete)\n\n\tr.Route(\"/{id}\", func(r chi.Router) {\n\t\t// r.Use(rs.TodoCtx) // lets have a todos map, and lets actually load/manipulate\n\t\tr.Get(\"/\", rs.Get)       // GET /todos/{id} - read a single todo by :id\n\t\tr.Put(\"/\", rs.Update)    // PUT /todos/{id} - update a single todo by :id\n\t\tr.Delete(\"/\", rs.Delete) // DELETE /todos/{id} - delete a single todo by :id\n\t\tr.Get(\"/sync\", rs.Sync)\n\t})\n\n\treturn r\n}\n\nfunc (rs todosResource) List(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"todos list of stuff..\"))\n}\n\nfunc (rs todosResource) Create(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"todos create\"))\n}\n\nfunc (rs todosResource) Get(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"todo get\"))\n}\n\nfunc (rs todosResource) Update(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"todo update\"))\n}\n\nfunc (rs todosResource) Delete(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"todo delete\"))\n}\n\nfunc (rs todosResource) Sync(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"todo sync\"))\n}\n"
  },
  {
    "path": "_examples/todos-resource/users.go",
    "content": "package main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\ntype usersResource struct{}\n\n// Routes creates a REST router for the todos resource\nfunc (rs usersResource) Routes() chi.Router {\n\tr := chi.NewRouter()\n\t// r.Use() // some middleware..\n\n\tr.Get(\"/\", rs.List)    // GET /users - read a list of users\n\tr.Post(\"/\", rs.Create) // POST /users - create a new user and persist it\n\tr.Put(\"/\", rs.Delete)\n\n\tr.Route(\"/{id}\", func(r chi.Router) {\n\t\t// r.Use(rs.TodoCtx) // lets have a users map, and lets actually load/manipulate\n\t\tr.Get(\"/\", rs.Get)       // GET /users/{id} - read a single user by :id\n\t\tr.Put(\"/\", rs.Update)    // PUT /users/{id} - update a single user by :id\n\t\tr.Delete(\"/\", rs.Delete) // DELETE /users/{id} - delete a single user by :id\n\t})\n\n\treturn r\n}\n\nfunc (rs usersResource) List(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"users list of stuff..\"))\n}\n\nfunc (rs usersResource) Create(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"users create\"))\n}\n\nfunc (rs usersResource) Get(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"user get\"))\n}\n\nfunc (rs usersResource) Update(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"user update\"))\n}\n\nfunc (rs usersResource) Delete(w http.ResponseWriter, r *http.Request) {\n\tw.Write([]byte(\"user delete\"))\n}\n"
  },
  {
    "path": "_examples/versions/data/article.go",
    "content": "package data\n\n// Article is runtime object, that's not meant to be sent via REST.\ntype Article struct {\n\tID                     int      `db:\"id\" json:\"id\" xml:\"id\"`\n\tTitle                  string   `db:\"title\" json:\"title\" xml:\"title\"`\n\tData                   []string `db:\"data,stringarray\" json:\"data\" xml:\"data\"`\n\tCustomDataForAuthUsers string   `db:\"custom_data\" json:\"-\" xml:\"-\"`\n}\n"
  },
  {
    "path": "_examples/versions/data/errors.go",
    "content": "package data\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\n\t\"github.com/go-chi/render\"\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"Unauthorized\")\n\tErrForbidden    = errors.New(\"Forbidden\")\n\tErrNotFound     = errors.New(\"Resource not found\")\n)\n\nfunc PresentError(r *http.Request, err error) (*http.Request, interface{}) {\n\tswitch err {\n\tcase ErrUnauthorized:\n\t\trender.Status(r, 401)\n\tcase ErrForbidden:\n\t\trender.Status(r, 403)\n\tcase ErrNotFound:\n\t\trender.Status(r, 404)\n\tdefault:\n\t\trender.Status(r, 500)\n\t}\n\treturn r, map[string]string{\"error\": err.Error()}\n}\n"
  },
  {
    "path": "_examples/versions/go.mod",
    "content": "module versions\n\ngo 1.16\n\nrequire (\n\tgithub.com/go-chi/chi/v5 v5.1.0\n\tgithub.com/go-chi/render v1.0.3\n)\n\nrequire github.com/ajg/form v1.5.1 // indirect\n"
  },
  {
    "path": "_examples/versions/go.sum",
    "content": "github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=\ngithub.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=\ngithub.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=\ngithub.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=\ngithub.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=\ngithub.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=\n"
  },
  {
    "path": "_examples/versions/main.go",
    "content": "// This example demonstrates the use of the render subpackage, with\n// a quick concept for how to support multiple api versions.\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/_examples/versions/data\"\n\tv1 \"github.com/go-chi/chi/v5/_examples/versions/presenter/v1\"\n\tv2 \"github.com/go-chi/chi/v5/_examples/versions/presenter/v2\"\n\tv3 \"github.com/go-chi/chi/v5/_examples/versions/presenter/v3\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n\t\"github.com/go-chi/render\"\n)\n\nfunc main() {\n\tr := chi.NewRouter()\n\n\tr.Use(middleware.RequestID)\n\tr.Use(middleware.Logger)\n\tr.Use(middleware.Recoverer)\n\n\t// API version 3.\n\tr.Route(\"/v3\", func(r chi.Router) {\n\t\tr.Use(apiVersionCtx(\"v3\"))\n\t\tr.Mount(\"/articles\", articleRouter())\n\t})\n\n\t// API version 2.\n\tr.Route(\"/v2\", func(r chi.Router) {\n\t\tr.Use(apiVersionCtx(\"v2\"))\n\t\tr.Mount(\"/articles\", articleRouter())\n\t})\n\n\t// API version 1.\n\tr.Route(\"/v1\", func(r chi.Router) {\n\t\tr.Use(randomErrorMiddleware) // Simulate random error, ie. version 1 is buggy.\n\t\tr.Use(apiVersionCtx(\"v1\"))\n\t\tr.Mount(\"/articles\", articleRouter())\n\t})\n\n\thttp.ListenAndServe(\":3333\", r)\n}\n\nfunc apiVersionCtx(version string) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tr = r.WithContext(context.WithValue(r.Context(), \"api.version\", version))\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n\nfunc articleRouter() http.Handler {\n\tr := chi.NewRouter()\n\tr.Get(\"/\", listArticles)\n\tr.Route(\"/{articleID}\", func(r chi.Router) {\n\t\tr.Get(\"/\", getArticle)\n\t\t// r.Put(\"/\", updateArticle)\n\t\t// r.Delete(\"/\", deleteArticle)\n\t})\n\treturn r\n}\n\nfunc listArticles(w http.ResponseWriter, r *http.Request) {\n\tarticles := make(chan render.Renderer, 5)\n\n\t// Load data asynchronously into the channel (simulate slow storage):\n\tgo func() {\n\t\tfor i := 1; i <= 10; i++ {\n\t\t\tarticle := &data.Article{\n\t\t\t\tID:                     i,\n\t\t\t\tTitle:                  fmt.Sprintf(\"Article #%v\", i),\n\t\t\t\tData:                   []string{\"one\", \"two\", \"three\", \"four\"},\n\t\t\t\tCustomDataForAuthUsers: \"secret data for auth'd users only\",\n\t\t\t}\n\n\t\t\tapiVersion := r.Context().Value(\"api.version\").(string)\n\t\t\tswitch apiVersion {\n\t\t\tcase \"v1\":\n\t\t\t\tarticles <- v1.NewArticleResponse(article)\n\t\t\tcase \"v2\":\n\t\t\t\tarticles <- v2.NewArticleResponse(article)\n\t\t\tdefault:\n\t\t\t\tarticles <- v3.NewArticleResponse(article)\n\t\t\t}\n\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t}\n\t\tclose(articles)\n\t}()\n\n\t// Start streaming data from the channel.\n\trender.Respond(w, r, articles)\n}\n\nfunc getArticle(w http.ResponseWriter, r *http.Request) {\n\t// Load article.\n\tif chi.URLParam(r, \"articleID\") != \"1\" {\n\t\trender.Respond(w, r, data.ErrNotFound)\n\t\treturn\n\t}\n\tarticle := &data.Article{\n\t\tID:                     1,\n\t\tTitle:                  \"Article #1\",\n\t\tData:                   []string{\"one\", \"two\", \"three\", \"four\"},\n\t\tCustomDataForAuthUsers: \"secret data for auth'd users only\",\n\t}\n\n\t// Simulate some context values:\n\t// 1. ?auth=true simulates authenticated session/user.\n\t// 2. ?error=true simulates random error.\n\tif r.URL.Query().Get(\"auth\") != \"\" {\n\t\tr = r.WithContext(context.WithValue(r.Context(), \"auth\", true))\n\t}\n\tif r.URL.Query().Get(\"error\") != \"\" {\n\t\trender.Respond(w, r, errors.New(\"error\"))\n\t\treturn\n\t}\n\n\tvar payload render.Renderer\n\n\tapiVersion := r.Context().Value(\"api.version\").(string)\n\tswitch apiVersion {\n\tcase \"v1\":\n\t\tpayload = v1.NewArticleResponse(article)\n\tcase \"v2\":\n\t\tpayload = v2.NewArticleResponse(article)\n\tdefault:\n\t\tpayload = v3.NewArticleResponse(article)\n\t}\n\n\trender.Render(w, r, payload)\n}\n\nfunc randomErrorMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\trand.Seed(time.Now().Unix())\n\n\t\t// One in three chance of random error.\n\t\tif rand.Int31n(3) == 0 {\n\t\t\terrors := []error{data.ErrUnauthorized, data.ErrForbidden, data.ErrNotFound}\n\t\t\trender.Respond(w, r, errors[rand.Intn(len(errors))])\n\t\t\treturn\n\t\t}\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "_examples/versions/presenter/v1/article.go",
    "content": "package v1\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5/_examples/versions/data\"\n)\n\n// Article presented in API version 1.\ntype Article struct {\n\t*data.Article\n\n\tData map[string]bool `json:\"data\" xml:\"data\"`\n}\n\nfunc (a *Article) Render(w http.ResponseWriter, r *http.Request) error {\n\treturn nil\n}\n\nfunc NewArticleResponse(article *data.Article) *Article {\n\treturn &Article{Article: article}\n}\n"
  },
  {
    "path": "_examples/versions/presenter/v2/article.go",
    "content": "package v2\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5/_examples/versions/data\"\n)\n\n// Article presented in API version 2.\ntype Article struct {\n\t// *v3.Article `json:\",inline\" xml:\",inline\"`\n\n\t*data.Article\n\n\t// Additional fields.\n\tSelfURL string `json:\"self_url\" xml:\"self_url\"`\n\n\t// Omitted fields.\n\tURL interface{} `json:\"url,omitempty\" xml:\"url,omitempty\"`\n}\n\nfunc (a *Article) Render(w http.ResponseWriter, r *http.Request) error {\n\ta.SelfURL = fmt.Sprintf(\"http://localhost:3333/v2?id=%v\", a.ID)\n\treturn nil\n}\n\nfunc NewArticleResponse(article *data.Article) *Article {\n\treturn &Article{Article: article}\n}\n"
  },
  {
    "path": "_examples/versions/presenter/v3/article.go",
    "content": "package v3\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5/_examples/versions/data\"\n)\n\n// Article presented in API version 2.\ntype Article struct {\n\t*data.Article `json:\",inline\" xml:\",inline\"`\n\n\t// Additional fields.\n\tURL        string `json:\"url\" xml:\"url\"`\n\tViewsCount int64  `json:\"views_count\" xml:\"views_count\"`\n\tAPIVersion string `json:\"api_version\" xml:\"api_version\"`\n\n\t// Omitted fields.\n\t// Show custom_data explicitly for auth'd users only.\n\tCustomDataForAuthUsers interface{} `json:\"custom_data,omitempty\" xml:\"custom_data,omitempty\"`\n}\n\nfunc (a *Article) Render(w http.ResponseWriter, r *http.Request) error {\n\ta.ViewsCount = rand.Int63n(100000)\n\ta.URL = fmt.Sprintf(\"http://localhost:3333/v3/?id=%v\", a.ID)\n\n\t// Only show to auth'd user.\n\tif _, ok := r.Context().Value(\"auth\").(bool); ok {\n\t\ta.CustomDataForAuthUsers = a.Article.CustomDataForAuthUsers\n\t}\n\n\treturn nil\n}\n\nfunc NewArticleResponse(article *data.Article) *Article {\n\treturn &Article{Article: article}\n}\n"
  },
  {
    "path": "chain.go",
    "content": "package chi\n\nimport \"net/http\"\n\n// Chain returns a Middlewares type from a slice of middleware handlers.\nfunc Chain(middlewares ...func(http.Handler) http.Handler) Middlewares {\n\treturn Middlewares(middlewares)\n}\n\n// Handler builds and returns a http.Handler from the chain of middlewares,\n// with `h http.Handler` as the final handler.\nfunc (mws Middlewares) Handler(h http.Handler) http.Handler {\n\treturn &ChainHandler{h, chain(mws, h), mws}\n}\n\n// HandlerFunc builds and returns a http.Handler from the chain of middlewares,\n// with `h http.Handler` as the final handler.\nfunc (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler {\n\treturn &ChainHandler{h, chain(mws, h), mws}\n}\n\n// ChainHandler is a http.Handler with support for handler composition and\n// execution.\ntype ChainHandler struct {\n\tEndpoint    http.Handler\n\tchain       http.Handler\n\tMiddlewares Middlewares\n}\n\nfunc (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tc.chain.ServeHTTP(w, r)\n}\n\n// chain builds a http.Handler composed of an inline middleware stack and endpoint\n// handler in the order they are passed.\nfunc chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler {\n\t// Return ahead of time if there aren't any middlewares for the chain\n\tif len(middlewares) == 0 {\n\t\treturn endpoint\n\t}\n\n\t// Wrap the end handler with the middleware chain\n\th := middlewares[len(middlewares)-1](endpoint)\n\tfor i := len(middlewares) - 2; i >= 0; i-- {\n\t\th = middlewares[i](h)\n\t}\n\n\treturn h\n}\n"
  },
  {
    "path": "chi.go",
    "content": "// Package chi is a small, idiomatic and composable router for building HTTP services.\n//\n// chi supports the four most recent major versions of Go.\n//\n// Example:\n//\n//\tpackage main\n//\n//\timport (\n//\t\t\"net/http\"\n//\n//\t\t\"github.com/go-chi/chi/v5\"\n//\t\t\"github.com/go-chi/chi/v5/middleware\"\n//\t)\n//\n//\tfunc main() {\n//\t\tr := chi.NewRouter()\n//\t\tr.Use(middleware.Logger)\n//\t\tr.Use(middleware.Recoverer)\n//\n//\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n//\t\t\tw.Write([]byte(\"root.\"))\n//\t\t})\n//\n//\t\thttp.ListenAndServe(\":3333\", r)\n//\t}\n//\n// See github.com/go-chi/chi/_examples/ for more in-depth examples.\n//\n// URL patterns allow for easy matching of path components in HTTP\n// requests. The matching components can then be accessed using\n// chi.URLParam(). All patterns must begin with a slash.\n//\n// A simple named placeholder {name} matches any sequence of characters\n// up to the next / or the end of the URL. Trailing slashes on paths must\n// be handled explicitly.\n//\n// A placeholder with a name followed by a colon allows a regular\n// expression match, for example {number:\\\\d+}. The regular expression\n// syntax is Go's normal regexp RE2 syntax, except that / will never be\n// matched. An anonymous regexp pattern is allowed, using an empty string\n// before the colon in the placeholder, such as {:\\\\d+}\n//\n// The special placeholder of asterisk matches the rest of the requested\n// URL. Any trailing characters in the pattern are ignored. This is the only\n// placeholder which will match / characters.\n//\n// Examples:\n//\n//\t\"/user/{name}\" matches \"/user/jsmith\" but not \"/user/jsmith/info\" or \"/user/jsmith/\"\n//\t\"/user/{name}/info\" matches \"/user/jsmith/info\"\n//\t\"/page/*\" matches \"/page/intro/latest\"\n//\t\"/page/{other}/latest\" also matches \"/page/intro/latest\"\n//\t\"/date/{yyyy:\\\\d\\\\d\\\\d\\\\d}/{mm:\\\\d\\\\d}/{dd:\\\\d\\\\d}\" matches \"/date/2017/04/01\"\npackage chi\n\nimport \"net/http\"\n\n// NewRouter returns a new Mux object that implements the Router interface.\nfunc NewRouter() *Mux {\n\treturn NewMux()\n}\n\n// Router consisting of the core routing methods used by chi's Mux,\n// using only the standard net/http.\ntype Router interface {\n\thttp.Handler\n\tRoutes\n\n\t// Use appends one or more middlewares onto the Router stack.\n\tUse(middlewares ...func(http.Handler) http.Handler)\n\n\t// With adds inline middlewares for an endpoint handler.\n\tWith(middlewares ...func(http.Handler) http.Handler) Router\n\n\t// Group adds a new inline-Router along the current routing\n\t// path, with a fresh middleware stack for the inline-Router.\n\tGroup(fn func(r Router)) Router\n\n\t// Route mounts a sub-Router along a `pattern`` string.\n\tRoute(pattern string, fn func(r Router)) Router\n\n\t// Mount attaches another http.Handler along ./pattern/*\n\tMount(pattern string, h http.Handler)\n\n\t// Handle and HandleFunc adds routes for `pattern` that matches\n\t// all HTTP methods.\n\tHandle(pattern string, h http.Handler)\n\tHandleFunc(pattern string, h http.HandlerFunc)\n\n\t// Method and MethodFunc adds routes for `pattern` that matches\n\t// the `method` HTTP method.\n\tMethod(method, pattern string, h http.Handler)\n\tMethodFunc(method, pattern string, h http.HandlerFunc)\n\n\t// HTTP-method routing along `pattern`\n\tConnect(pattern string, h http.HandlerFunc)\n\tDelete(pattern string, h http.HandlerFunc)\n\tGet(pattern string, h http.HandlerFunc)\n\tHead(pattern string, h http.HandlerFunc)\n\tOptions(pattern string, h http.HandlerFunc)\n\tPatch(pattern string, h http.HandlerFunc)\n\tPost(pattern string, h http.HandlerFunc)\n\tPut(pattern string, h http.HandlerFunc)\n\tTrace(pattern string, h http.HandlerFunc)\n\n\t// NotFound defines a handler to respond whenever a route could\n\t// not be found.\n\tNotFound(h http.HandlerFunc)\n\n\t// MethodNotAllowed defines a handler to respond whenever a method is\n\t// not allowed.\n\tMethodNotAllowed(h http.HandlerFunc)\n}\n\n// Routes interface adds two methods for router traversal, which is also\n// used by the `docgen` subpackage to generation documentation for Routers.\ntype Routes interface {\n\t// Routes returns the routing tree in an easily traversable structure.\n\tRoutes() []Route\n\n\t// Middlewares returns the list of middlewares in use by the router.\n\tMiddlewares() Middlewares\n\n\t// Match searches the routing tree for a handler that matches\n\t// the method/path - similar to routing a http request, but without\n\t// executing the handler thereafter.\n\tMatch(rctx *Context, method, path string) bool\n\n\t// Find searches the routing tree for the pattern that matches\n\t// the method/path.\n\tFind(rctx *Context, method, path string) string\n}\n\n// Middlewares type is a slice of standard middleware handlers with methods\n// to compose middleware chains and http.Handler's.\ntype Middlewares []func(http.Handler) http.Handler\n"
  },
  {
    "path": "context.go",
    "content": "package chi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// URLParam returns the url parameter from a http.Request object.\nfunc URLParam(r *http.Request, key string) string {\n\tif rctx := RouteContext(r.Context()); rctx != nil {\n\t\treturn rctx.URLParam(key)\n\t}\n\treturn \"\"\n}\n\n// URLParamFromCtx returns the url parameter from a http.Request Context.\nfunc URLParamFromCtx(ctx context.Context, key string) string {\n\tif rctx := RouteContext(ctx); rctx != nil {\n\t\treturn rctx.URLParam(key)\n\t}\n\treturn \"\"\n}\n\n// RouteContext returns chi's routing Context object from a\n// http.Request Context.\nfunc RouteContext(ctx context.Context) *Context {\n\tval, _ := ctx.Value(RouteCtxKey).(*Context)\n\treturn val\n}\n\n// NewRouteContext returns a new routing Context object.\nfunc NewRouteContext() *Context {\n\treturn &Context{}\n}\n\nvar (\n\t// RouteCtxKey is the context.Context key to store the request context.\n\tRouteCtxKey = &contextKey{\"RouteContext\"}\n)\n\n// Context is the default routing context set on the root node of a\n// request context to track route patterns, URL parameters and\n// an optional routing path.\ntype Context struct {\n\tRoutes Routes\n\n\t// parentCtx is the parent of this one, for using Context as a\n\t// context.Context directly. This is an optimization that saves\n\t// 1 allocation.\n\tparentCtx context.Context\n\n\t// Routing path/method override used during the route search.\n\t// See Mux#routeHTTP method.\n\tRoutePath   string\n\tRouteMethod string\n\n\t// URLParams are the stack of routeParams captured during the\n\t// routing lifecycle across a stack of sub-routers.\n\tURLParams RouteParams\n\n\t// Route parameters matched for the current sub-router. It is\n\t// intentionally unexported so it can't be tampered.\n\trouteParams RouteParams\n\n\t// The endpoint routing pattern that matched the request URI path\n\t// or `RoutePath` of the current sub-router. This value will update\n\t// during the lifecycle of a request passing through a stack of\n\t// sub-routers.\n\troutePattern string\n\n\t// Routing pattern stack throughout the lifecycle of the request,\n\t// across all connected routers. It is a record of all matching\n\t// patterns across a stack of sub-routers.\n\tRoutePatterns []string\n\n\tmethodsAllowed   []methodTyp // allowed methods in case of a 405\n\tmethodNotAllowed bool\n}\n\n// Reset a routing context to its initial state.\nfunc (x *Context) Reset() {\n\tx.Routes = nil\n\tx.RoutePath = \"\"\n\tx.RouteMethod = \"\"\n\tx.RoutePatterns = x.RoutePatterns[:0]\n\tx.URLParams.Keys = x.URLParams.Keys[:0]\n\tx.URLParams.Values = x.URLParams.Values[:0]\n\n\tx.routePattern = \"\"\n\tx.routeParams.Keys = x.routeParams.Keys[:0]\n\tx.routeParams.Values = x.routeParams.Values[:0]\n\tx.methodNotAllowed = false\n\tx.methodsAllowed = x.methodsAllowed[:0]\n\tx.parentCtx = nil\n}\n\n// URLParam returns the corresponding URL parameter value from the request\n// routing context.\nfunc (x *Context) URLParam(key string) string {\n\tfor k := len(x.URLParams.Keys) - 1; k >= 0; k-- {\n\t\tif x.URLParams.Keys[k] == key {\n\t\t\treturn x.URLParams.Values[k]\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// RoutePattern builds the routing pattern string for the particular\n// request, at the particular point during routing. This means, the value\n// will change throughout the execution of a request in a router. That is\n// why it's advised to only use this value after calling the next handler.\n//\n// For example,\n//\n//\tfunc Instrument(next http.Handler) http.Handler {\n//\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n//\t\t\tnext.ServeHTTP(w, r)\n//\t\t\troutePattern := chi.RouteContext(r.Context()).RoutePattern()\n//\t\t\tmeasure(w, r, routePattern)\n//\t\t})\n//\t}\nfunc (x *Context) RoutePattern() string {\n\tif x == nil {\n\t\treturn \"\"\n\t}\n\troutePattern := strings.Join(x.RoutePatterns, \"\")\n\troutePattern = replaceWildcards(routePattern)\n\tif routePattern != \"/\" {\n\t\troutePattern = strings.TrimSuffix(routePattern, \"//\")\n\t\troutePattern = strings.TrimSuffix(routePattern, \"/\")\n\t}\n\treturn routePattern\n}\n\n// replaceWildcards takes a route pattern and replaces all occurrences of\n// \"/*/\" with \"/\". It iteratively runs until no wildcards remain to\n// correctly handle consecutive wildcards.\nfunc replaceWildcards(p string) string {\n\tfor strings.Contains(p, \"/*/\") {\n\t\tp = strings.ReplaceAll(p, \"/*/\", \"/\")\n\t}\n\treturn p\n}\n\n// RouteParams is a structure to track URL routing parameters efficiently.\ntype RouteParams struct {\n\tKeys, Values []string\n}\n\n// Add will append a URL parameter to the end of the route param\nfunc (s *RouteParams) Add(key, value string) {\n\ts.Keys = append(s.Keys, key)\n\ts.Values = append(s.Values, value)\n}\n\n// contextKey is a value for use with context.WithValue. It's used as\n// a pointer so it fits in an interface{} without allocation. This technique\n// for defining context keys was copied from Go 1.7's new use of context in net/http.\ntype contextKey struct {\n\tname string\n}\n\nfunc (k *contextKey) String() string {\n\treturn \"chi context value \" + k.name\n}\n"
  },
  {
    "path": "context_test.go",
    "content": "package chi\n\nimport \"testing\"\n\n// TestRoutePattern tests correct in-the-middle wildcard removals.\n// If user organizes a router like this:\n//\n// (router.go)\n//\n//\tr.Route(\"/v1\", func(r chi.Router) {\n//\t\tr.Mount(\"/resources\", resourcesController{}.Router())\n//\t}\n//\n// (resources_controller.go)\n//\n//\tr.Route(\"/\", func(r chi.Router) {\n//\t\tr.Get(\"/{resource_id}\", getResource())\n//\t\t// other routes...\n//\t}\n//\n// This test checks how the route pattern is calculated\n// \"/v1/resources/{resource_id}\" (right)\n// \"/v1/resources/*/{resource_id}\" (wrong)\nfunc TestRoutePattern(t *testing.T) {\n\troutePatterns := []string{\n\t\t\"/v1/*\",\n\t\t\"/resources/*\",\n\t\t\"/{resource_id}\",\n\t}\n\n\tx := &Context{\n\t\tRoutePatterns: routePatterns,\n\t}\n\n\tif p := x.RoutePattern(); p != \"/v1/resources/{resource_id}\" {\n\t\tt.Fatal(\"unexpected route pattern: \" + p)\n\t}\n\n\tx.RoutePatterns = []string{\n\t\t\"/v1/*\",\n\t\t\"/resources/*\",\n\t\t// Additional wildcard, depending on the router structure of the user\n\t\t\"/*\",\n\t\t\"/{resource_id}\",\n\t}\n\n\t// Correctly removes in-the-middle wildcards instead of \"/v1/resources/*/{resource_id}\"\n\tif p := x.RoutePattern(); p != \"/v1/resources/{resource_id}\" {\n\t\tt.Fatal(\"unexpected route pattern: \" + p)\n\t}\n\n\tx.RoutePatterns = []string{\n\t\t\"/v1/*\",\n\t\t\"/resources/*\",\n\t\t// Even with many wildcards\n\t\t\"/*\",\n\t\t\"/*\",\n\t\t\"/*\",\n\t\t\"/{resource_id}/*\", // Keeping trailing wildcard\n\t}\n\n\t// Correctly removes in-the-middle wildcards instead of \"/v1/resources/*/*/{resource_id}/*\"\n\tif p := x.RoutePattern(); p != \"/v1/resources/{resource_id}/*\" {\n\t\tt.Fatal(\"unexpected route pattern: \" + p)\n\t}\n\n\tx.RoutePatterns = []string{\n\t\t\"/v1/*\",\n\t\t\"/resources/*\",\n\t\t// And respects asterisks as part of the paths\n\t\t\"/*special_path/*\",\n\t\t\"/with_asterisks*/*\",\n\t\t\"/{resource_id}\",\n\t}\n\n\t// Correctly removes in-the-middle wildcards instead of \"/v1/resourcesspecial_path/with_asterisks{resource_id}\"\n\tif p := x.RoutePattern(); p != \"/v1/resources/*special_path/with_asterisks*/{resource_id}\" {\n\t\tt.Fatal(\"unexpected route pattern: \" + p)\n\t}\n\n\t// Testing for the root route pattern\n\tx.RoutePatterns = []string{\"/\"}\n\t// It should just return \"/\" as the pattern\n\tif p := x.RoutePattern(); p != \"/\" {\n\t\tt.Fatal(\"unexpected route pattern for root: \" + p)\n\t}\n\n\t// Testing empty route pattern for nil context\n\tvar nilContext *Context\n\tif p := nilContext.RoutePattern(); p != \"\" {\n\t\tt.Fatalf(\"unexpected non-empty route pattern for nil context: %q\", p)\n\t}\n}\n\n// TestReplaceWildcardsConsecutive ensures multiple consecutive wildcards are\n// collapsed into a single slash.\nfunc TestReplaceWildcardsConsecutive(t *testing.T) {\n\tif p := replaceWildcards(\"/foo/*/*/*/bar\"); p != \"/foo/bar\" {\n\t\tt.Fatalf(\"unexpected wildcard replacement: %s\", p)\n\t}\n\tif p := replaceWildcards(\"/foo/*/*/*/bar/*\"); p != \"/foo/bar/*\" {\n\t\tt.Fatalf(\"unexpected trailing wildcard behavior: %s\", p)\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/go-chi/chi/v5\n\n// Chi supports the four most recent major versions of Go.\n// See https://github.com/go-chi/chi/issues/963.\ngo 1.23\n"
  },
  {
    "path": "middleware/basic_auth.go",
    "content": "package middleware\n\nimport (\n\t\"crypto/subtle\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// BasicAuth implements a simple middleware handler for adding basic http auth to a route.\nfunc BasicAuth(realm string, creds map[string]string) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tuser, pass, ok := r.BasicAuth()\n\t\t\tif !ok {\n\t\t\t\tbasicAuthFailed(w, realm)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcredPass, credUserOk := creds[user]\n\t\t\tif !credUserOk || subtle.ConstantTimeCompare([]byte(pass), []byte(credPass)) != 1 {\n\t\t\t\tbasicAuthFailed(w, realm)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n\nfunc basicAuthFailed(w http.ResponseWriter, realm string) {\n\tw.Header().Add(\"WWW-Authenticate\", fmt.Sprintf(`Basic realm=\"%s\"`, realm))\n\tw.WriteHeader(http.StatusUnauthorized)\n}\n"
  },
  {
    "path": "middleware/clean_path.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"path\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\n// CleanPath middleware will clean out double slash mistakes from a user's request path.\n// For example, if a user requests /users//1 or //users////1 will both be treated as: /users/1\nfunc CleanPath(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\trctx := chi.RouteContext(r.Context())\n\n\t\troutePath := rctx.RoutePath\n\t\tif routePath == \"\" {\n\t\t\tif r.URL.RawPath != \"\" {\n\t\t\t\troutePath = r.URL.RawPath\n\t\t\t} else {\n\t\t\t\troutePath = r.URL.Path\n\t\t\t}\n\t\t\trctx.RoutePath = path.Clean(routePath)\n\t\t}\n\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "middleware/compress.go",
    "content": "package middleware\n\nimport (\n\t\"bufio\"\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n)\n\nvar defaultCompressibleContentTypes = []string{\n\t\"text/html\",\n\t\"text/css\",\n\t\"text/plain\",\n\t\"text/javascript\",\n\t\"application/javascript\",\n\t\"application/x-javascript\",\n\t\"application/json\",\n\t\"application/atom+xml\",\n\t\"application/rss+xml\",\n\t\"image/svg+xml\",\n}\n\n// Compress is a middleware that compresses response\n// body of a given content types to a data format based\n// on Accept-Encoding request header. It uses a given\n// compression level.\n//\n// NOTE: make sure to set the Content-Type header on your response\n// otherwise this middleware will not compress the response body. For ex, in\n// your handler you should set w.Header().Set(\"Content-Type\", http.DetectContentType(yourBody))\n// or set it manually.\n//\n// Passing a compression level of 5 is sensible value\nfunc Compress(level int, types ...string) func(next http.Handler) http.Handler {\n\tcompressor := NewCompressor(level, types...)\n\treturn compressor.Handler\n}\n\n// Compressor represents a set of encoding configurations.\ntype Compressor struct {\n\t// The mapping of encoder names to encoder functions.\n\tencoders map[string]EncoderFunc\n\t// The mapping of pooled encoders to pools.\n\tpooledEncoders map[string]*sync.Pool\n\t// The set of content types allowed to be compressed.\n\tallowedTypes     map[string]struct{}\n\tallowedWildcards map[string]struct{}\n\t// The list of encoders in order of decreasing precedence.\n\tencodingPrecedence []string\n\tlevel              int // The compression level.\n}\n\n// NewCompressor creates a new Compressor that will handle encoding responses.\n//\n// The level should be one of the ones defined in the flate package.\n// The types are the content types that are allowed to be compressed.\nfunc NewCompressor(level int, types ...string) *Compressor {\n\t// If types are provided, set those as the allowed types. If none are\n\t// provided, use the default list.\n\tallowedTypes := make(map[string]struct{})\n\tallowedWildcards := make(map[string]struct{})\n\tif len(types) > 0 {\n\t\tfor _, t := range types {\n\t\t\tif strings.Contains(strings.TrimSuffix(t, \"/*\"), \"*\") {\n\t\t\t\tpanic(fmt.Sprintf(\"middleware/compress: Unsupported content-type wildcard pattern '%s'. Only '/*' supported\", t))\n\t\t\t}\n\t\t\tif before, ok := strings.CutSuffix(t, \"/*\"); ok {\n\t\t\t\tallowedWildcards[before] = struct{}{}\n\t\t\t} else {\n\t\t\t\tallowedTypes[t] = struct{}{}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor _, t := range defaultCompressibleContentTypes {\n\t\t\tallowedTypes[t] = struct{}{}\n\t\t}\n\t}\n\n\tc := &Compressor{\n\t\tlevel:            level,\n\t\tencoders:         make(map[string]EncoderFunc),\n\t\tpooledEncoders:   make(map[string]*sync.Pool),\n\t\tallowedTypes:     allowedTypes,\n\t\tallowedWildcards: allowedWildcards,\n\t}\n\n\t// Set the default encoders.  The precedence order uses the reverse\n\t// ordering that the encoders were added. This means adding new encoders\n\t// will move them to the front of the order.\n\t//\n\t// TODO:\n\t// lzma: Opera.\n\t// sdch: Chrome, Android. Gzip output + dictionary header.\n\t// br:   Brotli, see https://github.com/go-chi/chi/pull/326\n\n\t// HTTP 1.1 \"deflate\" (RFC 2616) stands for DEFLATE data (RFC 1951)\n\t// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32\n\t// checksum compared to CRC-32 used in \"gzip\" and thus is faster.\n\t//\n\t// But.. some old browsers (MSIE, Safari 5.1) incorrectly expect\n\t// raw DEFLATE data only, without the mentioned zlib wrapper.\n\t// Because of this major confusion, most modern browsers try it\n\t// both ways, first looking for zlib headers.\n\t// Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548\n\t//\n\t// The list of browsers having problems is quite big, see:\n\t// http://zoompf.com/blog/2012/02/lose-the-wait-http-compression\n\t// https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results\n\t//\n\t// That's why we prefer gzip over deflate. It's just more reliable\n\t// and not significantly slower than deflate.\n\tc.SetEncoder(\"deflate\", encoderDeflate)\n\n\t// TODO: Exception for old MSIE browsers that can't handle non-HTML?\n\t// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression\n\tc.SetEncoder(\"gzip\", encoderGzip)\n\n\t// NOTE: Not implemented, intentionally:\n\t// case \"compress\": // LZW. Deprecated.\n\t// case \"bzip2\":    // Too slow on-the-fly.\n\t// case \"zopfli\":   // Too slow on-the-fly.\n\t// case \"xz\":       // Too slow on-the-fly.\n\treturn c\n}\n\n// SetEncoder can be used to set the implementation of a compression algorithm.\n//\n// The encoding should be a standardised identifier. See:\n// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding\n//\n// For example, add the Brotli algorithm:\n//\n//\timport brotli_enc \"gopkg.in/kothar/brotli-go.v0/enc\"\n//\n//\tcompressor := middleware.NewCompressor(5, \"text/html\")\n//\tcompressor.SetEncoder(\"br\", func(w io.Writer, level int) io.Writer {\n//\t\tparams := brotli_enc.NewBrotliParams()\n//\t\tparams.SetQuality(level)\n//\t\treturn brotli_enc.NewBrotliWriter(params, w)\n//\t})\nfunc (c *Compressor) SetEncoder(encoding string, fn EncoderFunc) {\n\tencoding = strings.ToLower(encoding)\n\tif encoding == \"\" {\n\t\tpanic(\"the encoding can not be empty\")\n\t}\n\tif fn == nil {\n\t\tpanic(\"attempted to set a nil encoder function\")\n\t}\n\n\t// If we are adding a new encoder that is already registered, we have to\n\t// clear that one out first.\n\tdelete(c.pooledEncoders, encoding)\n\tdelete(c.encoders, encoding)\n\n\t// If the encoder supports Resetting (IoReseterWriter), then it can be pooled.\n\tencoder := fn(io.Discard, c.level)\n\tif _, ok := encoder.(ioResetterWriter); ok {\n\t\tpool := &sync.Pool{\n\t\t\tNew: func() interface{} {\n\t\t\t\treturn fn(io.Discard, c.level)\n\t\t\t},\n\t\t}\n\t\tc.pooledEncoders[encoding] = pool\n\t}\n\t// If the encoder is not in the pooledEncoders, add it to the normal encoders.\n\tif _, ok := c.pooledEncoders[encoding]; !ok {\n\t\tc.encoders[encoding] = fn\n\t}\n\n\tfor i, v := range c.encodingPrecedence {\n\t\tif v == encoding {\n\t\t\tc.encodingPrecedence = append(c.encodingPrecedence[:i], c.encodingPrecedence[i+1:]...)\n\t\t}\n\t}\n\n\tc.encodingPrecedence = append([]string{encoding}, c.encodingPrecedence...)\n}\n\n// Handler returns a new middleware that will compress the response based on the\n// current Compressor.\nfunc (c *Compressor) Handler(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tencoder, encoding, cleanup := c.selectEncoder(r.Header, w)\n\n\t\tcw := &compressResponseWriter{\n\t\t\tResponseWriter:   w,\n\t\t\tw:                w,\n\t\t\tcontentTypes:     c.allowedTypes,\n\t\t\tcontentWildcards: c.allowedWildcards,\n\t\t\tencoding:         encoding,\n\t\t\tcompressible:     false, // determined in post-handler\n\t\t}\n\t\tif encoder != nil {\n\t\t\tcw.w = encoder\n\t\t}\n\t\t// Re-add the encoder to the pool if applicable.\n\t\tdefer cleanup()\n\t\tdefer cw.Close()\n\n\t\tnext.ServeHTTP(cw, r)\n\t})\n}\n\n// selectEncoder returns the encoder, the name of the encoder, and a closer function.\nfunc (c *Compressor) selectEncoder(h http.Header, w io.Writer) (io.Writer, string, func()) {\n\theader := h.Get(\"Accept-Encoding\")\n\n\t// Parse the names of all accepted algorithms from the header.\n\taccepted := strings.Split(strings.ToLower(header), \",\")\n\n\t// Find supported encoder by accepted list by precedence\n\tfor _, name := range c.encodingPrecedence {\n\t\tif matchAcceptEncoding(accepted, name) {\n\t\t\tif pool, ok := c.pooledEncoders[name]; ok {\n\t\t\t\tencoder := pool.Get().(ioResetterWriter)\n\t\t\t\tcleanup := func() {\n\t\t\t\t\tpool.Put(encoder)\n\t\t\t\t}\n\t\t\t\tencoder.Reset(w)\n\t\t\t\treturn encoder, name, cleanup\n\n\t\t\t}\n\t\t\tif fn, ok := c.encoders[name]; ok {\n\t\t\t\treturn fn(w, c.level), name, func() {}\n\t\t\t}\n\t\t}\n\n\t}\n\n\t// No encoder found to match the accepted encoding\n\treturn nil, \"\", func() {}\n}\n\nfunc matchAcceptEncoding(accepted []string, encoding string) bool {\n\tfor _, v := range accepted {\n\t\tif strings.Contains(v, encoding) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// An EncoderFunc is a function that wraps the provided io.Writer with a\n// streaming compression algorithm and returns it.\n//\n// In case of failure, the function should return nil.\ntype EncoderFunc func(w io.Writer, level int) io.Writer\n\n// Interface for types that allow resetting io.Writers.\ntype ioResetterWriter interface {\n\tio.Writer\n\tReset(w io.Writer)\n}\n\ntype compressResponseWriter struct {\n\thttp.ResponseWriter\n\n\t// The streaming encoder writer to be used if there is one. Otherwise,\n\t// this is just the normal writer.\n\tw                io.Writer\n\tcontentTypes     map[string]struct{}\n\tcontentWildcards map[string]struct{}\n\tencoding         string\n\twroteHeader      bool\n\tcompressible     bool\n}\n\nfunc (cw *compressResponseWriter) isCompressible() bool {\n\t// Parse the first part of the Content-Type response header.\n\tcontentType := cw.Header().Get(\"Content-Type\")\n\tcontentType, _, _ = strings.Cut(contentType, \";\")\n\n\t// Is the content type compressible?\n\tif _, ok := cw.contentTypes[contentType]; ok {\n\t\treturn true\n\t}\n\tif contentType, _, hadSlash := strings.Cut(contentType, \"/\"); hadSlash {\n\t\t_, ok := cw.contentWildcards[contentType]\n\t\treturn ok\n\t}\n\treturn false\n}\n\nfunc (cw *compressResponseWriter) WriteHeader(code int) {\n\tif cw.wroteHeader {\n\t\tcw.ResponseWriter.WriteHeader(code) // Allow multiple calls to propagate.\n\t\treturn\n\t}\n\tcw.wroteHeader = true\n\tdefer cw.ResponseWriter.WriteHeader(code)\n\n\t// Already compressed data?\n\tif cw.Header().Get(\"Content-Encoding\") != \"\" {\n\t\treturn\n\t}\n\n\tif !cw.isCompressible() {\n\t\tcw.compressible = false\n\t\treturn\n\t}\n\n\tif cw.encoding != \"\" {\n\t\tcw.compressible = true\n\t\tcw.Header().Set(\"Content-Encoding\", cw.encoding)\n\t\tcw.Header().Add(\"Vary\", \"Accept-Encoding\")\n\n\t\t// The content-length after compression is unknown\n\t\tcw.Header().Del(\"Content-Length\")\n\t}\n}\n\nfunc (cw *compressResponseWriter) Write(p []byte) (int, error) {\n\tif !cw.wroteHeader {\n\t\tcw.WriteHeader(http.StatusOK)\n\t}\n\n\treturn cw.writer().Write(p)\n}\n\nfunc (cw *compressResponseWriter) writer() io.Writer {\n\tif cw.compressible {\n\t\treturn cw.w\n\t}\n\treturn cw.ResponseWriter\n}\n\ntype compressFlusher interface {\n\tFlush() error\n}\n\nfunc (cw *compressResponseWriter) Flush() {\n\tif f, ok := cw.writer().(http.Flusher); ok {\n\t\tf.Flush()\n\t}\n\t// If the underlying writer has a compression flush signature,\n\t// call this Flush() method instead\n\tif f, ok := cw.writer().(compressFlusher); ok {\n\t\tf.Flush()\n\n\t\t// Also flush the underlying response writer\n\t\tif f, ok := cw.ResponseWriter.(http.Flusher); ok {\n\t\t\tf.Flush()\n\t\t}\n\t}\n}\n\nfunc (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\tif hj, ok := cw.writer().(http.Hijacker); ok {\n\t\treturn hj.Hijack()\n\t}\n\treturn nil, nil, errors.New(\"chi/middleware: http.Hijacker is unavailable on the writer\")\n}\n\nfunc (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error {\n\tif ps, ok := cw.writer().(http.Pusher); ok {\n\t\treturn ps.Push(target, opts)\n\t}\n\treturn errors.New(\"chi/middleware: http.Pusher is unavailable on the writer\")\n}\n\nfunc (cw *compressResponseWriter) Close() error {\n\tif c, ok := cw.writer().(io.WriteCloser); ok {\n\t\treturn c.Close()\n\t}\n\treturn errors.New(\"chi/middleware: io.WriteCloser is unavailable on the writer\")\n}\n\nfunc (cw *compressResponseWriter) Unwrap() http.ResponseWriter {\n\treturn cw.ResponseWriter\n}\n\nfunc encoderGzip(w io.Writer, level int) io.Writer {\n\tgw, err := gzip.NewWriterLevel(w, level)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn gw\n}\n\nfunc encoderDeflate(w io.Writer, level int) io.Writer {\n\tdw, err := flate.NewWriter(w, level)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn dw\n}\n"
  },
  {
    "path": "middleware/compress_test.go",
    "content": "package middleware\n\nimport (\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestCompressor(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tcompressor := NewCompressor(5, \"text/html\", \"text/css\")\n\tif len(compressor.encoders) != 0 || len(compressor.pooledEncoders) != 2 {\n\t\tt.Errorf(\"gzip and deflate should be pooled\")\n\t}\n\n\tcompressor.SetEncoder(\"nop\", func(w io.Writer, _ int) io.Writer {\n\t\treturn w\n\t})\n\n\tif len(compressor.encoders) != 1 {\n\t\tt.Errorf(\"nop encoder should be stored in the encoders map\")\n\t}\n\n\tr.Use(compressor.Handler)\n\n\tr.Get(\"/gethtml\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\tw.Write([]byte(\"textstring\"))\n\t})\n\n\tr.Get(\"/getcss\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\tw.Write([]byte(\"textstring\"))\n\t})\n\n\tr.Get(\"/getplain\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\tw.Write([]byte(\"textstring\"))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\ttests := []struct {\n\t\tname              string\n\t\tpath              string\n\t\texpectedEncoding  string\n\t\tacceptedEncodings []string\n\t}{\n\t\t{\n\t\t\tname:              \"no expected encodings due to no accepted encodings\",\n\t\t\tpath:              \"/gethtml\",\n\t\t\tacceptedEncodings: nil,\n\t\t\texpectedEncoding:  \"\",\n\t\t},\n\t\t{\n\t\t\tname:              \"no expected encodings due to content type\",\n\t\t\tpath:              \"/getplain\",\n\t\t\tacceptedEncodings: nil,\n\t\t\texpectedEncoding:  \"\",\n\t\t},\n\t\t{\n\t\t\tname:              \"gzip is only encoding\",\n\t\t\tpath:              \"/gethtml\",\n\t\t\tacceptedEncodings: []string{\"gzip\"},\n\t\t\texpectedEncoding:  \"gzip\",\n\t\t},\n\t\t{\n\t\t\tname:              \"gzip is preferred over deflate\",\n\t\t\tpath:              \"/getcss\",\n\t\t\tacceptedEncodings: []string{\"gzip\", \"deflate\"},\n\t\t\texpectedEncoding:  \"gzip\",\n\t\t},\n\t\t{\n\t\t\tname:              \"deflate is used\",\n\t\t\tpath:              \"/getcss\",\n\t\t\tacceptedEncodings: []string{\"deflate\"},\n\t\t\texpectedEncoding:  \"deflate\",\n\t\t},\n\t\t{\n\n\t\t\tname:              \"nop is preferred\",\n\t\t\tpath:              \"/getcss\",\n\t\t\tacceptedEncodings: []string{\"nop, gzip, deflate\"},\n\t\t\texpectedEncoding:  \"nop\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresp, respString := testRequestWithAcceptedEncodings(t, ts, \"GET\", tc.path, tc.acceptedEncodings...)\n\t\t\tif respString != \"textstring\" {\n\t\t\t\tt.Errorf(\"response text doesn't match; expected:%q, got:%q\", \"textstring\", respString)\n\t\t\t}\n\t\t\tif got := resp.Header.Get(\"Content-Encoding\"); got != tc.expectedEncoding {\n\t\t\t\tt.Errorf(\"expected encoding %q but got %q\", tc.expectedEncoding, got)\n\t\t\t}\n\n\t\t})\n\n\t}\n}\n\nfunc TestCompressorWildcards(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\trecover    string\n\t\ttypes      []string\n\t\ttypesCount int\n\t\twcCount    int\n\t}{\n\t\t{\n\t\t\tname:       \"defaults\",\n\t\t\ttypesCount: 10,\n\t\t},\n\t\t{\n\t\t\tname:       \"no wildcard\",\n\t\t\ttypes:      []string{\"text/plain\", \"text/html\"},\n\t\t\ttypesCount: 2,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid wildcard #1\",\n\t\t\ttypes:   []string{\"audio/*wav\"},\n\t\t\trecover: \"middleware/compress: Unsupported content-type wildcard pattern 'audio/*wav'. Only '/*' supported\",\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid wildcard #2\",\n\t\t\ttypes:   []string{\"application*/*\"},\n\t\t\trecover: \"middleware/compress: Unsupported content-type wildcard pattern 'application*/*'. Only '/*' supported\",\n\t\t},\n\t\t{\n\t\t\tname:    \"valid wildcard\",\n\t\t\ttypes:   []string{\"text/*\"},\n\t\t\twcCount: 1,\n\t\t},\n\t\t{\n\t\t\tname:       \"mixed\",\n\t\t\ttypes:      []string{\"audio/wav\", \"text/*\"},\n\t\t\ttypesCount: 1,\n\t\t\twcCount:    1,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif tt.recover == \"\" {\n\t\t\t\t\ttt.recover = \"<nil>\"\n\t\t\t\t}\n\t\t\t\tif r := recover(); tt.recover != fmt.Sprintf(\"%v\", r) {\n\t\t\t\t\tt.Errorf(\"Unexpected value recovered: %v\", r)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tcompressor := NewCompressor(5, tt.types...)\n\t\t\tif len(compressor.allowedTypes) != tt.typesCount {\n\t\t\t\tt.Errorf(\"expected %d allowedTypes, got %d\", tt.typesCount, len(compressor.allowedTypes))\n\t\t\t}\n\t\t\tif len(compressor.allowedWildcards) != tt.wcCount {\n\t\t\t\tt.Errorf(\"expected %d allowedWildcards, got %d\", tt.wcCount, len(compressor.allowedWildcards))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc testRequestWithAcceptedEncodings(t *testing.T, ts *httptest.Server, method, path string, encodings ...string) (*http.Response, string) {\n\treq, err := http.NewRequest(method, ts.URL+path, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\tif len(encodings) > 0 {\n\t\tencodingsString := strings.Join(encodings, \",\")\n\t\treq.Header.Set(\"Accept-Encoding\", encodingsString)\n\t}\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\n\trespBody := decodeResponseBody(t, resp)\n\tdefer resp.Body.Close()\n\n\treturn resp, respBody\n}\n\nfunc decodeResponseBody(t *testing.T, resp *http.Response) string {\n\tvar reader io.ReadCloser\n\tswitch resp.Header.Get(\"Content-Encoding\") {\n\tcase \"gzip\":\n\t\tvar err error\n\t\treader, err = gzip.NewReader(resp.Body)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\tcase \"deflate\":\n\t\treader = flate.NewReader(resp.Body)\n\tdefault:\n\t\treader = resp.Body\n\t}\n\trespBody, err := io.ReadAll(reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn \"\"\n\t}\n\treader.Close()\n\n\treturn string(respBody)\n}\n"
  },
  {
    "path": "middleware/content_charset.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"slices\"\n\t\"strings\"\n)\n\n// ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match.\n// An empty charset will allow requests with no Content-Type header or no specified charset.\nfunc ContentCharset(charsets ...string) func(next http.Handler) http.Handler {\n\tfor i, c := range charsets {\n\t\tcharsets[i] = strings.ToLower(c)\n\t}\n\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif !contentEncoding(r.Header.Get(\"Content-Type\"), charsets...) {\n\t\t\t\tw.WriteHeader(http.StatusUnsupportedMediaType)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n\n// Check the content encoding against a list of acceptable values.\nfunc contentEncoding(ce string, charsets ...string) bool {\n\t_, ce = split(strings.ToLower(ce), \";\")\n\t_, ce = split(ce, \"charset=\")\n\tce, _ = split(ce, \";\")\n\treturn slices.Contains(charsets, ce)\n}\n\n// Split a string in two parts, cleaning any whitespace.\nfunc split(str, sep string) (string, string) {\n\ta, b, found := strings.Cut(str, sep)\n\ta = strings.TrimSpace(a)\n\tif found {\n\t\tb = strings.TrimSpace(b)\n\t}\n\n\treturn a, b\n}\n"
  },
  {
    "path": "middleware/content_charset_test.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestContentCharset(t *testing.T) {\n\tt.Parallel()\n\n\tvar tests = []struct {\n\t\tname                string\n\t\tinputValue          string\n\t\tinputContentCharset []string\n\t\twant                int\n\t}{\n\t\t{\n\t\t\t\"should accept requests with a matching charset\",\n\t\t\t\"application/json; charset=UTF-8\",\n\t\t\t[]string{\"UTF-8\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should be case-insensitive\",\n\t\t\t\"application/json; charset=utf-8\",\n\t\t\t[]string{\"UTF-8\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should accept requests with a matching charset with extra values\",\n\t\t\t\"application/json; foo=bar; charset=UTF-8; spam=eggs\",\n\t\t\t[]string{\"UTF-8\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should accept requests with a matching charset when multiple charsets are supported\",\n\t\t\t\"text/xml; charset=UTF-8\",\n\t\t\t[]string{\"UTF-8\", \"Latin-1\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should accept requests with no charset if empty charset headers are allowed\",\n\t\t\t\"text/xml\",\n\t\t\t[]string{\"UTF-8\", \"\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should not accept requests with no charset if empty charset headers are not allowed\",\n\t\t\t\"text/xml\",\n\t\t\t[]string{\"UTF-8\"},\n\t\t\thttp.StatusUnsupportedMediaType,\n\t\t},\n\t\t{\n\t\t\t\"should not accept requests with a mismatching charset\",\n\t\t\t\"text/plain; charset=Latin-1\",\n\t\t\t[]string{\"UTF-8\"},\n\t\t\thttp.StatusUnsupportedMediaType,\n\t\t},\n\t\t{\n\t\t\t\"should not accept requests with a mismatching charset even if empty charsets are allowed\",\n\t\t\t\"text/plain; charset=Latin-1\",\n\t\t\t[]string{\"UTF-8\", \"\"},\n\t\t\thttp.StatusUnsupportedMediaType,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tvar tt = tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tvar recorder = httptest.NewRecorder()\n\n\t\t\tvar r = chi.NewRouter()\n\t\t\tr.Use(ContentCharset(tt.inputContentCharset...))\n\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {})\n\n\t\t\tvar req, _ = http.NewRequest(\"GET\", \"/\", nil)\n\t\t\treq.Header.Set(\"Content-Type\", tt.inputValue)\n\n\t\t\tr.ServeHTTP(recorder, req)\n\t\t\tvar res = recorder.Result()\n\n\t\t\tif res.StatusCode != tt.want {\n\t\t\t\tt.Errorf(\"response is incorrect, got %d, want %d\", recorder.Code, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSplit(t *testing.T) {\n\tt.Parallel()\n\n\tvar s1, s2 = split(\"  type1;type2  \", \";\")\n\n\tif s1 != \"type1\" || s2 != \"type2\" {\n\t\tt.Errorf(\"Want type1, type2 got %s, %s\", s1, s2)\n\t}\n\n\ts1, s2 = split(\"type1  \", \";\")\n\n\tif s1 != \"type1\" {\n\t\tt.Errorf(\"Want \\\"type1\\\" got \\\"%s\\\"\", s1)\n\t}\n\tif s2 != \"\" {\n\t\tt.Errorf(\"Want empty string got \\\"%s\\\"\", s2)\n\t}\n}\n\nfunc TestContentEncoding(t *testing.T) {\n\tt.Parallel()\n\n\tif !contentEncoding(\"application/json; foo=bar; charset=utf-8; spam=eggs\", []string{\"utf-8\"}...) {\n\t\tt.Error(\"Want true, got false\")\n\t}\n\n\tif contentEncoding(\"text/plain; charset=latin-1\", []string{\"utf-8\"}...) {\n\t\tt.Error(\"Want false, got true\")\n\t}\n\n\tif !contentEncoding(\"text/xml; charset=UTF-8\", []string{\"latin-1\", \"utf-8\"}...) {\n\t\tt.Error(\"Want true, got false\")\n\t}\n}\n"
  },
  {
    "path": "middleware/content_encoding.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// AllowContentEncoding enforces a whitelist of request Content-Encoding otherwise responds\n// with a 415 Unsupported Media Type status.\nfunc AllowContentEncoding(contentEncoding ...string) func(next http.Handler) http.Handler {\n\tallowedEncodings := make(map[string]struct{}, len(contentEncoding))\n\tfor _, encoding := range contentEncoding {\n\t\tallowedEncodings[strings.TrimSpace(strings.ToLower(encoding))] = struct{}{}\n\t}\n\treturn func(next http.Handler) http.Handler {\n\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\trequestEncodings := r.Header[\"Content-Encoding\"]\n\t\t\t// skip check for empty content body or no Content-Encoding\n\t\t\tif r.ContentLength == 0 {\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// All encodings in the request must be allowed\n\t\t\tfor _, encoding := range requestEncodings {\n\t\t\t\tif _, ok := allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))]; !ok {\n\t\t\t\t\tw.WriteHeader(http.StatusUnsupportedMediaType)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tnext.ServeHTTP(w, r)\n\t\t}\n\t\treturn http.HandlerFunc(fn)\n\t}\n}\n"
  },
  {
    "path": "middleware/content_encoding_test.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestContentEncodingMiddleware(t *testing.T) {\n\tt.Parallel()\n\n\t// support for:\n\t// Content-Encoding: gzip\n\t// Content-Encoding: deflate\n\t// Content-Encoding: gzip, deflate\n\t// Content-Encoding: deflate, gzip\n\tmiddleware := AllowContentEncoding(\"deflate\", \"gzip\")\n\n\ttests := []struct {\n\t\tname           string\n\t\tencodings      []string\n\t\texpectedStatus int\n\t}{\n\t\t{\n\t\t\tname:           \"Support no encoding\",\n\t\t\tencodings:      []string{},\n\t\t\texpectedStatus: 200,\n\t\t},\n\t\t{\n\t\t\tname:           \"Support gzip encoding\",\n\t\t\tencodings:      []string{\"gzip\"},\n\t\t\texpectedStatus: 200,\n\t\t},\n\t\t{\n\t\t\tname:           \"No support for br encoding\",\n\t\t\tencodings:      []string{\"br\"},\n\t\t\texpectedStatus: 415,\n\t\t},\n\t\t{\n\t\t\tname:           \"Support for gzip and deflate encoding\",\n\t\t\tencodings:      []string{\"gzip\", \"deflate\"},\n\t\t\texpectedStatus: 200,\n\t\t},\n\t\t{\n\t\t\tname:           \"Support for deflate and gzip encoding\",\n\t\t\tencodings:      []string{\"deflate\", \"gzip\"},\n\t\t\texpectedStatus: 200,\n\t\t},\n\t\t{\n\t\t\tname:           \"No support for deflate and br encoding\",\n\t\t\tencodings:      []string{\"deflate\", \"br\"},\n\t\t\texpectedStatus: 415,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tvar tt = tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tbody := []byte(\"This is my content. There are many like this but this one is mine\")\n\t\t\tr := httptest.NewRequest(\"POST\", \"/\", bytes.NewReader(body))\n\t\t\tfor _, encoding := range tt.encodings {\n\t\t\t\tr.Header.Set(\"Content-Encoding\", encoding)\n\t\t\t}\n\n\t\t\tw := httptest.NewRecorder()\n\t\t\trouter := chi.NewRouter()\n\t\t\trouter.Use(middleware)\n\t\t\trouter.Post(\"/\", func(w http.ResponseWriter, r *http.Request) {})\n\n\t\t\trouter.ServeHTTP(w, r)\n\t\t\tres := w.Result()\n\t\t\tif res.StatusCode != tt.expectedStatus {\n\t\t\t\tt.Errorf(\"response is incorrect, got %d, want %d\", w.Code, tt.expectedStatus)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/content_type.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// SetHeader is a convenience handler to set a response header key/value\nfunc SetHeader(key, value string) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Header().Set(key, value)\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n\n// AllowContentType enforces a whitelist of request Content-Types otherwise responds\n// with a 415 Unsupported Media Type status.\nfunc AllowContentType(contentTypes ...string) func(http.Handler) http.Handler {\n\tallowedContentTypes := make(map[string]struct{}, len(contentTypes))\n\tfor _, ctype := range contentTypes {\n\t\tallowedContentTypes[strings.TrimSpace(strings.ToLower(ctype))] = struct{}{}\n\t}\n\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.ContentLength == 0 {\n\t\t\t\t// Skip check for empty content body\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ts, _, _ := strings.Cut(r.Header.Get(\"Content-Type\"), \";\")\n\t\t\ts = strings.ToLower(strings.TrimSpace(s))\n\n\t\t\tif _, ok := allowedContentTypes[s]; ok {\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tw.WriteHeader(http.StatusUnsupportedMediaType)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/content_type_test.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestContentType(t *testing.T) {\n\tt.Parallel()\n\n\tvar tests = []struct {\n\t\tname                string\n\t\tinputValue          string\n\t\tallowedContentTypes []string\n\t\twant                int\n\t}{\n\t\t{\n\t\t\t\"should accept requests with a matching content type\",\n\t\t\t\"application/json; charset=UTF-8\",\n\t\t\t[]string{\"application/json\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should accept requests with a matching content type no charset\",\n\t\t\t\"application/json\",\n\t\t\t[]string{\"application/json\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should accept requests with a matching content-type with extra values\",\n\t\t\t\"application/json; foo=bar; charset=UTF-8; spam=eggs\",\n\t\t\t[]string{\"application/json\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should accept requests with a matching content type when multiple content types are supported\",\n\t\t\t\"text/xml; charset=UTF-8\",\n\t\t\t[]string{\"application/json\", \"text/xml\"},\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t{\n\t\t\t\"should not accept requests with a mismatching content type\",\n\t\t\t\"text/plain; charset=latin-1\",\n\t\t\t[]string{\"application/json\"},\n\t\t\thttp.StatusUnsupportedMediaType,\n\t\t},\n\t\t{\n\t\t\t\"should not accept requests with a mismatching content type even if multiple content types are allowed\",\n\t\t\t\"text/plain; charset=Latin-1\",\n\t\t\t[]string{\"application/json\", \"text/xml\"},\n\t\t\thttp.StatusUnsupportedMediaType,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tvar tt = tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\trecorder := httptest.NewRecorder()\n\n\t\t\tr := chi.NewRouter()\n\t\t\tr.Use(AllowContentType(tt.allowedContentTypes...))\n\t\t\tr.Post(\"/\", func(w http.ResponseWriter, r *http.Request) {})\n\n\t\t\tbody := []byte(\"This is my content. There are many like this but this one is mine\")\n\t\t\treq := httptest.NewRequest(\"POST\", \"/\", bytes.NewReader(body))\n\t\t\treq.Header.Set(\"Content-Type\", tt.inputValue)\n\n\t\t\tr.ServeHTTP(recorder, req)\n\t\t\tres := recorder.Result()\n\n\t\t\tif res.StatusCode != tt.want {\n\t\t\t\tt.Errorf(\"response is incorrect, got %d, want %d\", recorder.Code, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/get_head.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\n// GetHead automatically route undefined HEAD requests to GET handlers.\nfunc GetHead(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method == \"HEAD\" {\n\t\t\trctx := chi.RouteContext(r.Context())\n\t\t\troutePath := rctx.RoutePath\n\t\t\tif routePath == \"\" {\n\t\t\t\tif r.URL.RawPath != \"\" {\n\t\t\t\t\troutePath = r.URL.RawPath\n\t\t\t\t} else {\n\t\t\t\t\troutePath = r.URL.Path\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Temporary routing context to look-ahead before routing the request\n\t\t\ttctx := chi.NewRouteContext()\n\n\t\t\t// Attempt to find a HEAD handler for the routing path, if not found, traverse\n\t\t\t// the router as through its a GET route, but proceed with the request\n\t\t\t// with the HEAD method.\n\t\t\tif !rctx.Routes.Match(tctx, \"HEAD\", routePath) {\n\t\t\t\trctx.RouteMethod = \"GET\"\n\t\t\t\trctx.RoutePath = routePath\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "middleware/get_head_test.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestGetHead(t *testing.T) {\n\tr := chi.NewRouter()\n\tr.Use(GetHead)\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Test\", \"yes\")\n\t\tw.Write([]byte(\"bye\"))\n\t})\n\tr.Route(\"/articles\", func(r chi.Router) {\n\t\tr.Get(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tid := chi.URLParam(r, \"id\")\n\t\t\tw.Header().Set(\"X-Article\", id)\n\t\t\tw.Write([]byte(\"article:\" + id))\n\t\t})\n\t})\n\tr.Route(\"/users\", func(r chi.Router) {\n\t\tr.Head(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Header().Set(\"X-User\", \"-\")\n\t\t\tw.Write([]byte(\"user\"))\n\t\t})\n\t\tr.Get(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tid := chi.URLParam(r, \"id\")\n\t\t\tw.Header().Set(\"X-User\", id)\n\t\t\tw.Write([]byte(\"user:\" + id))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/hi\", nil); body != \"bye\" {\n\t\tt.Fatal(body)\n\t}\n\tif req, body := testRequest(t, ts, \"HEAD\", \"/hi\", nil); body != \"\" || req.Header.Get(\"X-Test\") != \"yes\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/\", nil); body != \"404 page not found\\n\" {\n\t\tt.Fatal(body)\n\t}\n\tif req, body := testRequest(t, ts, \"HEAD\", \"/\", nil); body != \"\" || req.StatusCode != 404 {\n\t\tt.Fatal(body)\n\t}\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/articles/5\", nil); body != \"article:5\" {\n\t\tt.Fatal(body)\n\t}\n\tif req, body := testRequest(t, ts, \"HEAD\", \"/articles/5\", nil); body != \"\" || req.Header.Get(\"X-Article\") != \"5\" {\n\t\tt.Fatalf(\"expecting X-Article header '5' but got '%s'\", req.Header.Get(\"X-Article\"))\n\t}\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/users/1\", nil); body != \"user:1\" {\n\t\tt.Fatal(body)\n\t}\n\tif req, body := testRequest(t, ts, \"HEAD\", \"/users/1\", nil); body != \"\" || req.Header.Get(\"X-User\") != \"-\" {\n\t\tt.Fatalf(\"expecting X-User header '-' but got '%s'\", req.Header.Get(\"X-User\"))\n\t}\n}\n"
  },
  {
    "path": "middleware/heartbeat.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// Heartbeat endpoint middleware useful to setting up a path like\n// `/ping` that load balancers or uptime testing external services\n// can make a request before hitting any routes. It's also convenient\n// to place this above ACL middlewares as well.\nfunc Heartbeat(endpoint string) func(http.Handler) http.Handler {\n\tf := func(h http.Handler) http.Handler {\n\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif (r.Method == \"GET\" || r.Method == \"HEAD\") && strings.EqualFold(r.URL.Path, endpoint) {\n\t\t\t\tw.Header().Set(\"Content-Type\", \"text/plain\")\n\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t\tw.Write([]byte(\".\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\th.ServeHTTP(w, r)\n\t\t}\n\t\treturn http.HandlerFunc(fn)\n\t}\n\treturn f\n}\n"
  },
  {
    "path": "middleware/logger.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n)\n\nvar (\n\t// LogEntryCtxKey is the context.Context key to store the request log entry.\n\tLogEntryCtxKey = &contextKey{\"LogEntry\"}\n\n\t// DefaultLogger is called by the Logger middleware handler to log each request.\n\t// Its made a package-level variable so that it can be reconfigured for custom\n\t// logging configurations.\n\tDefaultLogger func(next http.Handler) http.Handler\n)\n\n// Logger is a middleware that logs the start and end of each request, along\n// with some useful data about what was requested, what the response status was,\n// and how long it took to return. When standard output is a TTY, Logger will\n// print in color, otherwise it will print in black and white. Logger prints a\n// request ID if one is provided.\n//\n// Alternatively, look at https://github.com/goware/httplog for a more in-depth\n// http logger with structured logging support.\n//\n// IMPORTANT NOTE: Logger should go before any other middleware that may change\n// the response, such as middleware.Recoverer. Example:\n//\n//\tr := chi.NewRouter()\n//\tr.Use(middleware.Logger)        // <--<< Logger should come before Recoverer\n//\tr.Use(middleware.Recoverer)\n//\tr.Get(\"/\", handler)\nfunc Logger(next http.Handler) http.Handler {\n\treturn DefaultLogger(next)\n}\n\n// RequestLogger returns a logger handler using a custom LogFormatter.\nfunc RequestLogger(f LogFormatter) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\tentry := f.NewLogEntry(r)\n\t\t\tww := NewWrapResponseWriter(w, r.ProtoMajor)\n\n\t\t\tt1 := time.Now()\n\t\t\tdefer func() {\n\t\t\t\tentry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), nil)\n\t\t\t}()\n\n\t\t\tnext.ServeHTTP(ww, WithLogEntry(r, entry))\n\t\t}\n\t\treturn http.HandlerFunc(fn)\n\t}\n}\n\n// LogFormatter initiates the beginning of a new LogEntry per request.\n// See DefaultLogFormatter for an example implementation.\ntype LogFormatter interface {\n\tNewLogEntry(r *http.Request) LogEntry\n}\n\n// LogEntry records the final log when a request completes.\n// See defaultLogEntry for an example implementation.\ntype LogEntry interface {\n\tWrite(status, bytes int, header http.Header, elapsed time.Duration, extra interface{})\n\tPanic(v interface{}, stack []byte)\n}\n\n// GetLogEntry returns the in-context LogEntry for a request.\nfunc GetLogEntry(r *http.Request) LogEntry {\n\tentry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry)\n\treturn entry\n}\n\n// WithLogEntry sets the in-context LogEntry for a request.\nfunc WithLogEntry(r *http.Request, entry LogEntry) *http.Request {\n\tr = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry))\n\treturn r\n}\n\n// LoggerInterface accepts printing to stdlib logger or compatible logger.\ntype LoggerInterface interface {\n\tPrint(v ...interface{})\n}\n\n// DefaultLogFormatter is a simple logger that implements a LogFormatter.\ntype DefaultLogFormatter struct {\n\tLogger  LoggerInterface\n\tNoColor bool\n}\n\n// NewLogEntry creates a new LogEntry for the request.\nfunc (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {\n\tuseColor := !l.NoColor\n\tentry := &defaultLogEntry{\n\t\tDefaultLogFormatter: l,\n\t\trequest:             r,\n\t\tbuf:                 &bytes.Buffer{},\n\t\tuseColor:            useColor,\n\t}\n\n\treqID := GetReqID(r.Context())\n\tif reqID != \"\" {\n\t\tcW(entry.buf, useColor, nYellow, \"[%s] \", reqID)\n\t}\n\tcW(entry.buf, useColor, nCyan, \"\\\"\")\n\tcW(entry.buf, useColor, bMagenta, \"%s \", r.Method)\n\n\tscheme := \"http\"\n\tif r.TLS != nil {\n\t\tscheme = \"https\"\n\t}\n\tcW(entry.buf, useColor, nCyan, \"%s://%s%s %s\\\" \", scheme, r.Host, r.RequestURI, r.Proto)\n\n\tentry.buf.WriteString(\"from \")\n\tentry.buf.WriteString(r.RemoteAddr)\n\tentry.buf.WriteString(\" - \")\n\n\treturn entry\n}\n\ntype defaultLogEntry struct {\n\t*DefaultLogFormatter\n\trequest  *http.Request\n\tbuf      *bytes.Buffer\n\tuseColor bool\n}\n\nfunc (l *defaultLogEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {\n\tswitch {\n\tcase status < 200:\n\t\tcW(l.buf, l.useColor, bBlue, \"%03d\", status)\n\tcase status < 300:\n\t\tcW(l.buf, l.useColor, bGreen, \"%03d\", status)\n\tcase status < 400:\n\t\tcW(l.buf, l.useColor, bCyan, \"%03d\", status)\n\tcase status < 500:\n\t\tcW(l.buf, l.useColor, bYellow, \"%03d\", status)\n\tdefault:\n\t\tcW(l.buf, l.useColor, bRed, \"%03d\", status)\n\t}\n\n\tcW(l.buf, l.useColor, bBlue, \" %dB\", bytes)\n\n\tl.buf.WriteString(\" in \")\n\tif elapsed < 500*time.Millisecond {\n\t\tcW(l.buf, l.useColor, nGreen, \"%s\", elapsed)\n\t} else if elapsed < 5*time.Second {\n\t\tcW(l.buf, l.useColor, nYellow, \"%s\", elapsed)\n\t} else {\n\t\tcW(l.buf, l.useColor, nRed, \"%s\", elapsed)\n\t}\n\n\tl.Logger.Print(l.buf.String())\n}\n\nfunc (l *defaultLogEntry) Panic(v interface{}, stack []byte) {\n\tPrintPrettyStack(v)\n}\n\nfunc init() {\n\tcolor := true\n\tif runtime.GOOS == \"windows\" {\n\t\tcolor = false\n\t}\n\tDefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, \"\", log.LstdFlags), NoColor: !color})\n}\n"
  },
  {
    "path": "middleware/logger_test.go",
    "content": "package middleware\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype testLoggerWriter struct {\n\t*httptest.ResponseRecorder\n}\n\nfunc (cw testLoggerWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\treturn nil, nil, nil\n}\n\nfunc TestRequestLogger(t *testing.T) {\n\ttestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, ok := w.(http.Hijacker)\n\t\tif !ok {\n\t\t\tt.Errorf(\"http.Hijacker is unavailable on the writer. add the interface methods.\")\n\t\t}\n\t})\n\n\tr := httptest.NewRequest(\"GET\", \"/\", nil)\n\tw := testLoggerWriter{\n\t\tResponseRecorder: httptest.NewRecorder(),\n\t}\n\n\thandler := DefaultLogger(testHandler)\n\thandler.ServeHTTP(w, r)\n}\n\nfunc TestRequestLoggerReadFrom(t *testing.T) {\n\tdata := []byte(\"file data\")\n\ttestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\thttp.ServeContent(w, r, \"file\", time.Time{}, bytes.NewReader(data))\n\t})\n\n\tr := httptest.NewRequest(\"GET\", \"/\", nil)\n\tw := httptest.NewRecorder()\n\n\thandler := DefaultLogger(testHandler)\n\thandler.ServeHTTP(w, r)\n\n\tassertEqual(t, data, w.Body.Bytes())\n}\n"
  },
  {
    "path": "middleware/maybe.go",
    "content": "package middleware\n\nimport \"net/http\"\n\n// Maybe middleware will allow you to change the flow of the middleware stack execution depending on return\n// value of maybeFn(request). This is useful for example if you'd like to skip a middleware handler if\n// a request does not satisfy the maybeFn logic.\nfunc Maybe(mw func(http.Handler) http.Handler, maybeFn func(r *http.Request) bool) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif maybeFn(r) {\n\t\t\t\tmw(next).ServeHTTP(w, r)\n\t\t\t} else {\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/middleware.go",
    "content": "package middleware\n\nimport \"net/http\"\n\n// New will create a new middleware handler from a http.Handler.\nfunc New(h http.Handler) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\th.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n\n// contextKey is a value for use with context.WithValue. It's used as\n// a pointer so it fits in an interface{} without allocation. This technique\n// for defining context keys was copied from Go 1.7's new use of context in net/http.\ntype contextKey struct {\n\tname string\n}\n\nfunc (k *contextKey) String() string {\n\treturn \"chi/middleware context value \" + k.name\n}\n"
  },
  {
    "path": "middleware/middleware_test.go",
    "content": "package middleware\n\nimport (\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"path\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar testdataDir string\n\nfunc init() {\n\t_, filename, _, _ := runtime.Caller(0)\n\ttestdataDir = path.Join(path.Dir(filename), \"/../testdata\")\n}\n\nfunc TestWrapWriterHTTP2(t *testing.T) {\n\thandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Proto != \"HTTP/2.0\" {\n\t\t\tt.Fatalf(\"request proto should be HTTP/2.0 but was %s\", r.Proto)\n\t\t}\n\t\t_, fl := w.(http.Flusher)\n\t\tif !fl {\n\t\t\tt.Fatal(\"request should have been a http.Flusher\")\n\t\t}\n\t\t_, hj := w.(http.Hijacker)\n\t\tif hj {\n\t\t\tt.Fatal(\"request should not have been a http.Hijacker\")\n\t\t}\n\t\t_, rf := w.(io.ReaderFrom)\n\t\tif rf {\n\t\t\tt.Fatal(\"request should not have been an io.ReaderFrom\")\n\t\t}\n\t\t_, ps := w.(http.Pusher)\n\t\tif !ps {\n\t\t\tt.Fatal(\"request should have been a http.Pusher\")\n\t\t}\n\n\t\tw.Write([]byte(\"OK\"))\n\t})\n\n\twmw := func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tnext.ServeHTTP(NewWrapResponseWriter(w, r.ProtoMajor), r)\n\t\t})\n\t}\n\n\tserver := http.Server{\n\t\tAddr:    \":7072\",\n\t\tHandler: wmw(handler),\n\t}\n\t// By serving over TLS, we get HTTP2 requests\n\tgo server.ListenAndServeTLS(testdataDir+\"/cert.pem\", testdataDir+\"/key.pem\")\n\tdefer server.Close()\n\t// We need the server to start before making the request\n\ttime.Sleep(100 * time.Millisecond)\n\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\t// The certificates we are using are self signed\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t},\n\t\t\tForceAttemptHTTP2: true,\n\t\t},\n\t}\n\n\tresp, err := client.Get(\"https://localhost:7072\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get server: %v\", err)\n\t}\n\tif resp.StatusCode != 200 {\n\t\tt.Fatalf(\"non 200 response: %v\", resp.StatusCode)\n\t}\n}\n\nfunc testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) {\n\treq, err := http.NewRequest(method, ts.URL+path, body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\tdefer resp.Body.Close()\n\n\treturn resp, string(respBody)\n}\n\nfunc testRequestNoRedirect(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) {\n\treq, err := http.NewRequest(method, ts.URL+path, body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\n\t// http client that doesn't redirect\n\thttpClient := &http.Client{\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\treturn http.ErrUseLastResponse\n\t\t},\n\t}\n\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\tdefer resp.Body.Close()\n\n\treturn resp, string(respBody)\n}\n\nfunc assertNoError(t *testing.T, err error) {\n\tt.Helper()\n\tif err != nil {\n\t\tt.Fatalf(\"expecting no error\")\n\t}\n}\n\nfunc assertError(t *testing.T, err error) {\n\tt.Helper()\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n}\n\nfunc assertEqual(t *testing.T, a, b interface{}) {\n\tt.Helper()\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Fatalf(\"expecting values to be equal but got: '%v' and '%v'\", a, b)\n\t}\n}\n"
  },
  {
    "path": "middleware/nocache.go",
    "content": "package middleware\n\n// Ported from Goji's middleware, source:\n// https://github.com/zenazn/goji/tree/master/web/middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n)\n\n// Unix epoch time\nvar epoch = time.Unix(0, 0).UTC().Format(http.TimeFormat)\n\n// Taken from https://github.com/mytrile/nocache\nvar noCacheHeaders = map[string]string{\n\t\"Expires\":         epoch,\n\t\"Cache-Control\":   \"no-cache, no-store, no-transform, must-revalidate, private, max-age=0\",\n\t\"Pragma\":          \"no-cache\",\n\t\"X-Accel-Expires\": \"0\",\n}\n\nvar etagHeaders = []string{\n\t\"ETag\",\n\t\"If-Modified-Since\",\n\t\"If-Match\",\n\t\"If-None-Match\",\n\t\"If-Range\",\n\t\"If-Unmodified-Since\",\n}\n\n// NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent\n// a router (or subrouter) from being cached by an upstream proxy and/or client.\n//\n// As per http://wiki.nginx.org/HttpProxyModule - NoCache sets:\n//\n//\tExpires: Thu, 01 Jan 1970 00:00:00 UTC\n//\tCache-Control: no-cache, private, max-age=0\n//\tX-Accel-Expires: 0\n//\tPragma: no-cache (for HTTP/1.0 proxies/clients)\nfunc NoCache(h http.Handler) http.Handler {\n\tfn := func(w http.ResponseWriter, r *http.Request) {\n\n\t\t// Delete any ETag headers that may have been set\n\t\tfor _, v := range etagHeaders {\n\t\t\tif r.Header.Get(v) != \"\" {\n\t\t\t\tr.Header.Del(v)\n\t\t\t}\n\t\t}\n\n\t\t// Set our NoCache headers\n\t\tfor k, v := range noCacheHeaders {\n\t\t\tw.Header().Set(k, v)\n\t\t}\n\n\t\th.ServeHTTP(w, r)\n\t}\n\n\treturn http.HandlerFunc(fn)\n}\n"
  },
  {
    "path": "middleware/page_route.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PageRoute is a simple middleware which allows you to route a static GET request\n// at the middleware stack level.\nfunc PageRoute(path string, handler http.Handler) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == \"GET\" && strings.EqualFold(r.URL.Path, path) {\n\t\t\t\thandler.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/path_rewrite.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// PathRewrite is a simple middleware which allows you to rewrite the request URL path.\nfunc PathRewrite(old, new string) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tr.URL.Path = strings.Replace(r.URL.Path, old, new, 1)\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/profiler.go",
    "content": "//go:build !tinygo\n// +build !tinygo\n\npackage middleware\n\nimport (\n\t\"expvar\"\n\t\"net/http\"\n\t\"net/http/pprof\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\n// Profiler is a convenient subrouter used for mounting net/http/pprof. ie.\n//\n//\tfunc MyService() http.Handler {\n//\t\tr := chi.NewRouter()\n//\t\t// ..middlewares\n//\t\tr.Mount(\"/debug\", middleware.Profiler())\n//\t\t// ..routes\n//\t\treturn r\n//\t}\nfunc Profiler() http.Handler {\n\tr := chi.NewRouter()\n\tr.Use(NoCache)\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\thttp.Redirect(w, r, r.RequestURI+\"/pprof/\", http.StatusMovedPermanently)\n\t})\n\tr.HandleFunc(\"/pprof\", func(w http.ResponseWriter, r *http.Request) {\n\t\thttp.Redirect(w, r, r.RequestURI+\"/\", http.StatusMovedPermanently)\n\t})\n\n\tr.HandleFunc(\"/pprof/*\", pprof.Index)\n\tr.HandleFunc(\"/pprof/cmdline\", pprof.Cmdline)\n\tr.HandleFunc(\"/pprof/profile\", pprof.Profile)\n\tr.HandleFunc(\"/pprof/symbol\", pprof.Symbol)\n\tr.HandleFunc(\"/pprof/trace\", pprof.Trace)\n\tr.Handle(\"/vars\", expvar.Handler())\n\n\tr.Handle(\"/pprof/goroutine\", pprof.Handler(\"goroutine\"))\n\tr.Handle(\"/pprof/threadcreate\", pprof.Handler(\"threadcreate\"))\n\tr.Handle(\"/pprof/mutex\", pprof.Handler(\"mutex\"))\n\tr.Handle(\"/pprof/heap\", pprof.Handler(\"heap\"))\n\tr.Handle(\"/pprof/block\", pprof.Handler(\"block\"))\n\tr.Handle(\"/pprof/allocs\", pprof.Handler(\"allocs\"))\n\n\treturn r\n}\n"
  },
  {
    "path": "middleware/realip.go",
    "content": "package middleware\n\n// Ported from Goji's middleware, source:\n// https://github.com/zenazn/goji/tree/master/web/middleware\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n)\n\nvar trueClientIP = http.CanonicalHeaderKey(\"True-Client-IP\")\nvar xForwardedFor = http.CanonicalHeaderKey(\"X-Forwarded-For\")\nvar xRealIP = http.CanonicalHeaderKey(\"X-Real-IP\")\n\n// RealIP is a middleware that sets a http.Request's RemoteAddr to the results\n// of parsing either the True-Client-IP, X-Real-IP or the X-Forwarded-For headers\n// (in that order).\n//\n// This middleware should be inserted fairly early in the middleware stack to\n// ensure that subsequent layers (e.g., request loggers) which examine the\n// RemoteAddr will see the intended value.\n//\n// You should only use this middleware if you can trust the headers passed to\n// you (in particular, the three headers this middleware uses), for example\n// because you have placed a reverse proxy like HAProxy or nginx in front of\n// chi. If your reverse proxies are configured to pass along arbitrary header\n// values from the client, or if you use this middleware without a reverse\n// proxy, malicious clients will be able to make you very sad (or, depending on\n// how you're using RemoteAddr, vulnerable to an attack of some sort).\nfunc RealIP(h http.Handler) http.Handler {\n\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\tif rip := realIP(r); rip != \"\" {\n\t\t\tr.RemoteAddr = rip\n\t\t}\n\t\th.ServeHTTP(w, r)\n\t}\n\n\treturn http.HandlerFunc(fn)\n}\n\nfunc realIP(r *http.Request) string {\n\tvar ip string\n\n\tif tcip := r.Header.Get(trueClientIP); tcip != \"\" {\n\t\tip = tcip\n\t} else if xrip := r.Header.Get(xRealIP); xrip != \"\" {\n\t\tip = xrip\n\t} else if xff := r.Header.Get(xForwardedFor); xff != \"\" {\n\t\tip, _, _ = strings.Cut(xff, \",\")\n\t}\n\tif ip == \"\" || net.ParseIP(ip) == nil {\n\t\treturn \"\"\n\t}\n\treturn ip\n}\n"
  },
  {
    "path": "middleware/realip_test.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestXRealIP(t *testing.T) {\n\treq, _ := http.NewRequest(\"GET\", \"/\", nil)\n\treq.Header.Add(\"X-Real-IP\", \"100.100.100.100\")\n\tw := httptest.NewRecorder()\n\n\tr := chi.NewRouter()\n\tr.Use(RealIP)\n\n\trealIP := \"\"\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\trealIP = r.RemoteAddr\n\t\tw.Write([]byte(\"Hello World\"))\n\t})\n\tr.ServeHTTP(w, req)\n\n\tif w.Code != 200 {\n\t\tt.Fatal(\"Response Code should be 200\")\n\t}\n\n\tif realIP != \"100.100.100.100\" {\n\t\tt.Fatal(\"Test get real IP error.\")\n\t}\n}\n\nfunc TestXForwardForIP(t *testing.T) {\n\txForwardedForIPs := []string{\n\t\t\"100.100.100.100\",\n\t\t\"100.100.100.100, 200.200.200.200\",\n\t\t\"100.100.100.100,200.200.200.200\",\n\t}\n\n\tr := chi.NewRouter()\n\tr.Use(RealIP)\n\n\tfor _, v := range xForwardedForIPs {\n\t\treq, _ := http.NewRequest(\"GET\", \"/\", nil)\n\t\treq.Header.Add(\"X-Forwarded-For\", v)\n\n\t\tw := httptest.NewRecorder()\n\n\t\trealIP := \"\"\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\trealIP = r.RemoteAddr\n\t\t\tw.Write([]byte(\"Hello World\"))\n\t\t})\n\t\tr.ServeHTTP(w, req)\n\n\t\tif w.Code != 200 {\n\t\t\tt.Fatal(\"Response Code should be 200\")\n\t\t}\n\n\t\tif realIP != \"100.100.100.100\" {\n\t\t\tt.Fatal(\"Test get real IP error.\")\n\t\t}\n\t}\n}\n\nfunc TestXForwardForXRealIPPrecedence(t *testing.T) {\n\treq, _ := http.NewRequest(\"GET\", \"/\", nil)\n\treq.Header.Add(\"X-Forwarded-For\", \"0.0.0.0\")\n\treq.Header.Add(\"X-Real-IP\", \"100.100.100.100\")\n\tw := httptest.NewRecorder()\n\n\tr := chi.NewRouter()\n\tr.Use(RealIP)\n\n\trealIP := \"\"\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\trealIP = r.RemoteAddr\n\t\tw.Write([]byte(\"Hello World\"))\n\t})\n\tr.ServeHTTP(w, req)\n\n\tif w.Code != 200 {\n\t\tt.Fatal(\"Response Code should be 200\")\n\t}\n\n\tif realIP != \"100.100.100.100\" {\n\t\tt.Fatal(\"Test get real IP precedence error.\")\n\t}\n}\n\nfunc TestInvalidIP(t *testing.T) {\n\treq, _ := http.NewRequest(\"GET\", \"/\", nil)\n\treq.Header.Add(\"X-Real-IP\", \"100.100.100.1000\")\n\tw := httptest.NewRecorder()\n\n\tr := chi.NewRouter()\n\tr.Use(RealIP)\n\n\trealIP := \"\"\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\trealIP = r.RemoteAddr\n\t\tw.Write([]byte(\"Hello World\"))\n\t})\n\tr.ServeHTTP(w, req)\n\n\tif w.Code != 200 {\n\t\tt.Fatal(\"Response Code should be 200\")\n\t}\n\n\tif realIP != \"\" {\n\t\tt.Fatal(\"Invalid IP used.\")\n\t}\n}\n"
  },
  {
    "path": "middleware/recoverer.go",
    "content": "package middleware\n\n// The original work was derived from Goji's middleware, source:\n// https://github.com/zenazn/goji/tree/master/web/middleware\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strings\"\n)\n\n// Recoverer is a middleware that recovers from panics, logs the panic (and a\n// backtrace), and returns a HTTP 500 (Internal Server Error) status if\n// possible. Recoverer prints a request ID if one is provided.\n//\n// Alternatively, look at https://github.com/go-chi/httplog middleware pkgs.\nfunc Recoverer(next http.Handler) http.Handler {\n\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer func() {\n\t\t\tif rvr := recover(); rvr != nil {\n\t\t\t\tif rvr == http.ErrAbortHandler {\n\t\t\t\t\t// we don't recover http.ErrAbortHandler so the response\n\t\t\t\t\t// to the client is aborted, this should not be logged\n\t\t\t\t\tpanic(rvr)\n\t\t\t\t}\n\n\t\t\t\tlogEntry := GetLogEntry(r)\n\t\t\t\tif logEntry != nil {\n\t\t\t\t\tlogEntry.Panic(rvr, debug.Stack())\n\t\t\t\t} else {\n\t\t\t\t\tPrintPrettyStack(rvr)\n\t\t\t\t}\n\n\t\t\t\tif r.Header.Get(\"Connection\") != \"Upgrade\" {\n\t\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tnext.ServeHTTP(w, r)\n\t}\n\n\treturn http.HandlerFunc(fn)\n}\n\n// for ability to test the PrintPrettyStack function\nvar recovererErrorWriter io.Writer = os.Stderr\n\nfunc PrintPrettyStack(rvr interface{}) {\n\tdebugStack := debug.Stack()\n\ts := prettyStack{}\n\tout, err := s.parse(debugStack, rvr)\n\tif err == nil {\n\t\trecovererErrorWriter.Write(out)\n\t} else {\n\t\t// print stdlib output as a fallback\n\t\tos.Stderr.Write(debugStack)\n\t}\n}\n\ntype prettyStack struct {\n}\n\nfunc (s prettyStack) parse(debugStack []byte, rvr interface{}) ([]byte, error) {\n\tvar err error\n\tuseColor := true\n\tbuf := &bytes.Buffer{}\n\n\tcW(buf, false, bRed, \"\\n\")\n\tcW(buf, useColor, bCyan, \" panic: \")\n\tcW(buf, useColor, bBlue, \"%v\", rvr)\n\tcW(buf, false, bWhite, \"\\n \\n\")\n\n\t// process debug stack info\n\tstack := strings.Split(string(debugStack), \"\\n\")\n\tlines := []string{}\n\n\t// locate panic line, as we may have nested panics\n\tfor i := len(stack) - 1; i > 0; i-- {\n\t\tlines = append(lines, stack[i])\n\t\tif strings.HasPrefix(stack[i], \"panic(\") {\n\t\t\tlines = lines[0 : len(lines)-2] // remove boilerplate\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// reverse\n\tfor i := len(lines)/2 - 1; i >= 0; i-- {\n\t\topp := len(lines) - 1 - i\n\t\tlines[i], lines[opp] = lines[opp], lines[i]\n\t}\n\n\t// decorate\n\tfor i, line := range lines {\n\t\tlines[i], err = s.decorateLine(line, useColor, i)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tfor _, l := range lines {\n\t\tfmt.Fprintf(buf, \"%s\", l)\n\t}\n\treturn buf.Bytes(), nil\n}\n\nfunc (s prettyStack) decorateLine(line string, useColor bool, num int) (string, error) {\n\tline = strings.TrimSpace(line)\n\tif strings.HasPrefix(line, \"\\t\") || strings.Contains(line, \".go:\") {\n\t\treturn s.decorateSourceLine(line, useColor, num)\n\t}\n\tif strings.HasSuffix(line, \")\") {\n\t\treturn s.decorateFuncCallLine(line, useColor, num)\n\t}\n\tif strings.HasPrefix(line, \"\\t\") {\n\t\treturn strings.Replace(line, \"\\t\", \"      \", 1), nil\n\t}\n\treturn fmt.Sprintf(\"    %s\\n\", line), nil\n}\n\nfunc (s prettyStack) decorateFuncCallLine(line string, useColor bool, num int) (string, error) {\n\tidx := strings.LastIndex(line, \"(\")\n\tif idx < 0 {\n\t\treturn \"\", errors.New(\"not a func call line\")\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tpkg := line[0:idx]\n\t// addr := line[idx:]\n\tmethod := \"\"\n\n\tif idx := strings.LastIndex(pkg, string(os.PathSeparator)); idx < 0 {\n\t\tif idx := strings.Index(pkg, \".\"); idx > 0 {\n\t\t\tmethod = pkg[idx:]\n\t\t\tpkg = pkg[0:idx]\n\t\t}\n\t} else {\n\t\tmethod = pkg[idx+1:]\n\t\tpkg = pkg[0 : idx+1]\n\t\tif idx := strings.Index(method, \".\"); idx > 0 {\n\t\t\tpkg += method[0:idx]\n\t\t\tmethod = method[idx:]\n\t\t}\n\t}\n\tpkgColor := nYellow\n\tmethodColor := bGreen\n\n\tif num == 0 {\n\t\tcW(buf, useColor, bRed, \" -> \")\n\t\tpkgColor = bMagenta\n\t\tmethodColor = bRed\n\t} else {\n\t\tcW(buf, useColor, bWhite, \"    \")\n\t}\n\tcW(buf, useColor, pkgColor, \"%s\", pkg)\n\tcW(buf, useColor, methodColor, \"%s\\n\", method)\n\t// cW(buf, useColor, nBlack, \"%s\", addr)\n\treturn buf.String(), nil\n}\n\nfunc (s prettyStack) decorateSourceLine(line string, useColor bool, num int) (string, error) {\n\tidx := strings.LastIndex(line, \".go:\")\n\tif idx < 0 {\n\t\treturn \"\", errors.New(\"not a source line\")\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tpath := line[0 : idx+3]\n\tlineno := line[idx+3:]\n\n\tidx = strings.LastIndex(path, string(os.PathSeparator))\n\tdir := path[0 : idx+1]\n\tfile := path[idx+1:]\n\n\tidx = strings.Index(lineno, \" \")\n\tif idx > 0 {\n\t\tlineno = lineno[0:idx]\n\t}\n\tfileColor := bCyan\n\tlineColor := bGreen\n\n\tif num == 1 {\n\t\tcW(buf, useColor, bRed, \" ->   \")\n\t\tfileColor = bRed\n\t\tlineColor = bMagenta\n\t} else {\n\t\tcW(buf, false, bWhite, \"      \")\n\t}\n\tcW(buf, useColor, bWhite, \"%s\", dir)\n\tcW(buf, useColor, fileColor, \"%s\", file)\n\tcW(buf, useColor, lineColor, \"%s\", lineno)\n\tif num == 1 {\n\t\tcW(buf, false, bWhite, \"\\n\")\n\t}\n\tcW(buf, false, bWhite, \"\\n\")\n\n\treturn buf.String(), nil\n}\n"
  },
  {
    "path": "middleware/recoverer_test.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc panickingHandler(http.ResponseWriter, *http.Request) { panic(\"foo\") }\n\nfunc TestRecoverer(t *testing.T) {\n\tr := chi.NewRouter()\n\n\toldRecovererErrorWriter := recovererErrorWriter\n\tdefer func() { recovererErrorWriter = oldRecovererErrorWriter }()\n\tbuf := &bytes.Buffer{}\n\trecovererErrorWriter = buf\n\n\tr.Use(Recoverer)\n\tr.Get(\"/\", panickingHandler)\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tres, _ := testRequest(t, ts, \"GET\", \"/\", nil)\n\tassertEqual(t, res.StatusCode, http.StatusInternalServerError)\n\n\tlines := strings.Split(buf.String(), \"\\n\")\n\tfor _, line := range lines {\n\t\tif strings.HasPrefix(strings.TrimSpace(line), \"->\") {\n\t\t\tif !strings.Contains(line, \"panickingHandler\") {\n\t\t\t\tt.Fatalf(\"First func call line should refer to panickingHandler, but actual line:\\n%v\\n\", line)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\tt.Fatal(\"First func call line should start with ->.\")\n}\n\nfunc TestRecovererAbortHandler(t *testing.T) {\n\tdefer func() {\n\t\trcv := recover()\n\t\tif rcv != http.ErrAbortHandler {\n\t\t\tt.Fatalf(\"http.ErrAbortHandler should not be recovered\")\n\t\t}\n\t}()\n\n\tw := httptest.NewRecorder()\n\n\tr := chi.NewRouter()\n\tr.Use(Recoverer)\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tpanic(http.ErrAbortHandler)\n\t})\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tr.ServeHTTP(w, req)\n}\n"
  },
  {
    "path": "middleware/request_id.go",
    "content": "package middleware\n\n// Ported from Goji's middleware, source:\n// https://github.com/zenazn/goji/tree/master/web/middleware\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"sync/atomic\"\n)\n\n// Key to use when setting the request ID.\ntype ctxKeyRequestID int\n\n// RequestIDKey is the key that holds the unique request ID in a request context.\nconst RequestIDKey ctxKeyRequestID = 0\n\n// RequestIDHeader is the name of the HTTP Header which contains the request id.\n// Exported so that it can be changed by developers\nvar RequestIDHeader = \"X-Request-Id\"\n\nvar prefix string\nvar reqid atomic.Uint64\n\n// A quick note on the statistics here: we're trying to calculate the chance that\n// two randomly generated base62 prefixes will collide. We use the formula from\n// http://en.wikipedia.org/wiki/Birthday_problem\n//\n// P[m, n] \\approx 1 - e^{-m^2/2n}\n//\n// We ballpark an upper bound for $m$ by imagining (for whatever reason) a server\n// that restarts every second over 10 years, for $m = 86400 * 365 * 10 = 315360000$\n//\n// For a $k$ character base-62 identifier, we have $n(k) = 62^k$\n//\n// Plugging this in, we find $P[m, n(10)] \\approx 5.75%$, which is good enough for\n// our purposes, and is surely more than anyone would ever need in practice -- a\n// process that is rebooted a handful of times a day for a hundred years has less\n// than a millionth of a percent chance of generating two colliding IDs.\n\nfunc init() {\n\thostname, err := os.Hostname()\n\tif hostname == \"\" || err != nil {\n\t\thostname = \"localhost\"\n\t}\n\tvar buf [12]byte\n\tvar b64 string\n\tfor len(b64) < 10 {\n\t\trand.Read(buf[:])\n\t\tb64 = base64.StdEncoding.EncodeToString(buf[:])\n\t\tb64 = strings.NewReplacer(\"+\", \"\", \"/\", \"\").Replace(b64)\n\t}\n\n\tprefix = fmt.Sprintf(\"%s/%s\", hostname, b64[0:10])\n}\n\n// RequestID is a middleware that injects a request ID into the context of each\n// request. A request ID is a string of the form \"host.example.com/random-0001\",\n// where \"random\" is a base62 random string that uniquely identifies this go\n// process, and where the last number is an atomically incremented request\n// counter.\nfunc RequestID(next http.Handler) http.Handler {\n\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\tctx := r.Context()\n\t\trequestID := r.Header.Get(RequestIDHeader)\n\t\tif requestID == \"\" {\n\t\t\tmyid := reqid.Add(1)\n\t\t\trequestID = fmt.Sprintf(\"%s-%06d\", prefix, myid)\n\t\t}\n\t\tctx = context.WithValue(ctx, RequestIDKey, requestID)\n\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t}\n\treturn http.HandlerFunc(fn)\n}\n\n// GetReqID returns a request ID from the given context if one is present.\n// Returns the empty string if a request ID cannot be found.\nfunc GetReqID(ctx context.Context) string {\n\tif ctx == nil {\n\t\treturn \"\"\n\t}\n\tif reqID, ok := ctx.Value(RequestIDKey).(string); ok {\n\t\treturn reqID\n\t}\n\treturn \"\"\n}\n\n// NextRequestID generates the next request ID in the sequence.\nfunc NextRequestID() uint64 {\n\treturn reqid.Add(1)\n}\n"
  },
  {
    "path": "middleware/request_id_test.go",
    "content": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc maintainDefaultRequestID() func() {\n\toriginal := RequestIDHeader\n\n\treturn func() {\n\t\tRequestIDHeader = original\n\t}\n}\n\nfunc TestRequestID(t *testing.T) {\n\ttests := map[string]struct {\n\t\trequestIDHeader  string\n\t\trequest          func() *http.Request\n\t\texpectedResponse string\n\t}{\n\t\t\"Retrieves Request Id from default header\": {\n\t\t\t\"X-Request-Id\",\n\t\t\tfunc() *http.Request {\n\t\t\t\treq, _ := http.NewRequest(\"GET\", \"/\", nil)\n\t\t\t\treq.Header.Add(\"X-Request-Id\", \"req-123456\")\n\n\t\t\t\treturn req\n\t\t\t},\n\t\t\t\"RequestID: req-123456\",\n\t\t},\n\t\t\"Retrieves Request Id from custom header\": {\n\t\t\t\"X-Trace-Id\",\n\t\t\tfunc() *http.Request {\n\t\t\t\treq, _ := http.NewRequest(\"GET\", \"/\", nil)\n\t\t\t\treq.Header.Add(\"X-Trace-Id\", \"trace:abc123\")\n\n\t\t\t\treturn req\n\t\t\t},\n\t\t\t\"RequestID: trace:abc123\",\n\t\t},\n\t}\n\n\tdefer maintainDefaultRequestID()()\n\n\tfor _, test := range tests {\n\t\tw := httptest.NewRecorder()\n\n\t\tr := chi.NewRouter()\n\n\t\tRequestIDHeader = test.requestIDHeader\n\n\t\tr.Use(RequestID)\n\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\trequestID := GetReqID(r.Context())\n\t\t\tresponse := fmt.Sprintf(\"RequestID: %s\", requestID)\n\n\t\t\tw.Write([]byte(response))\n\t\t})\n\t\tr.ServeHTTP(w, test.request())\n\n\t\tif w.Body.String() != test.expectedResponse {\n\t\t\tt.Fatalf(\"RequestID was not the expected value\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "middleware/request_size.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n)\n\n// RequestSize is a middleware that will limit request sizes to a specified\n// number of bytes. It uses MaxBytesReader to do so.\nfunc RequestSize(bytes int64) func(http.Handler) http.Handler {\n\tf := func(h http.Handler) http.Handler {\n\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\tr.Body = http.MaxBytesReader(w, r.Body, bytes)\n\t\t\th.ServeHTTP(w, r)\n\t\t}\n\t\treturn http.HandlerFunc(fn)\n\t}\n\treturn f\n}\n"
  },
  {
    "path": "middleware/route_headers.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// RouteHeaders is a neat little header-based router that allows you to direct\n// the flow of a request through a middleware stack based on a request header.\n//\n// For example, lets say you'd like to setup multiple routers depending on the\n// request Host header, you could then do something as so:\n//\n//\tr := chi.NewRouter()\n//\trSubdomain := chi.NewRouter()\n//\tr.Use(middleware.RouteHeaders().\n//\t\tRoute(\"Host\", \"example.com\", middleware.New(r)).\n//\t\tRoute(\"Host\", \"*.example.com\", middleware.New(rSubdomain)).\n//\t\tHandler)\n//\tr.Get(\"/\", h)\n//\trSubdomain.Get(\"/\", h2)\n//\n// Another example, imagine you want to setup multiple CORS handlers, where for\n// your origin servers you allow authorized requests, but for third-party public\n// requests, authorization is disabled.\n//\n//\tr := chi.NewRouter()\n//\tr.Use(middleware.RouteHeaders().\n//\t\tRoute(\"Origin\", \"https://app.skyweaver.net\", cors.Handler(cors.Options{\n//\t\t\tAllowedOrigins:   []string{\"https://api.skyweaver.net\"},\n//\t\t\tAllowedMethods:   []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"},\n//\t\t\tAllowedHeaders:   []string{\"Accept\", \"Authorization\", \"Content-Type\"},\n//\t\t\tAllowCredentials: true, // <----------<<< allow credentials\n//\t\t})).\n//\t\tRoute(\"Origin\", \"*\", cors.Handler(cors.Options{\n//\t\t\tAllowedOrigins:   []string{\"*\"},\n//\t\t\tAllowedMethods:   []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"},\n//\t\t\tAllowedHeaders:   []string{\"Accept\", \"Content-Type\"},\n//\t\t\tAllowCredentials: false, // <----------<<< do not allow credentials\n//\t\t})).\n//\t\tHandler)\nfunc RouteHeaders() HeaderRouter {\n\treturn HeaderRouter{}\n}\n\ntype HeaderRouter map[string][]HeaderRoute\n\nfunc (hr HeaderRouter) Route(header, match string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter {\n\theader = strings.ToLower(header)\n\tk := hr[header]\n\tif k == nil {\n\t\thr[header] = []HeaderRoute{}\n\t}\n\thr[header] = append(hr[header], HeaderRoute{MatchOne: NewPattern(match), Middleware: middlewareHandler})\n\treturn hr\n}\n\nfunc (hr HeaderRouter) RouteAny(header string, match []string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter {\n\theader = strings.ToLower(header)\n\tk := hr[header]\n\tif k == nil {\n\t\thr[header] = []HeaderRoute{}\n\t}\n\tpatterns := []Pattern{}\n\tfor _, m := range match {\n\t\tpatterns = append(patterns, NewPattern(m))\n\t}\n\thr[header] = append(hr[header], HeaderRoute{MatchAny: patterns, Middleware: middlewareHandler})\n\treturn hr\n}\n\nfunc (hr HeaderRouter) RouteDefault(handler func(next http.Handler) http.Handler) HeaderRouter {\n\thr[\"*\"] = []HeaderRoute{{Middleware: handler}}\n\treturn hr\n}\n\nfunc (hr HeaderRouter) Handler(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif len(hr) == 0 {\n\t\t\t// skip if no routes set\n\t\t\tnext.ServeHTTP(w, r)\n\t\t\treturn\n\t\t}\n\n\t\t// find first matching header route, and continue\n\t\tfor header, matchers := range hr {\n\t\t\theaderValue := r.Header.Get(header)\n\t\t\tif headerValue == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\theaderValue = strings.ToLower(headerValue)\n\t\t\tfor _, matcher := range matchers {\n\t\t\t\tif matcher.IsMatch(headerValue) {\n\t\t\t\t\tmatcher.Middleware(next).ServeHTTP(w, r)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// if no match, check for \"*\" default route\n\t\tmatcher, ok := hr[\"*\"]\n\t\tif !ok || matcher[0].Middleware == nil {\n\t\t\tnext.ServeHTTP(w, r)\n\t\t\treturn\n\t\t}\n\t\tmatcher[0].Middleware(next).ServeHTTP(w, r)\n\t})\n}\n\ntype HeaderRoute struct {\n\tMiddleware func(next http.Handler) http.Handler\n\tMatchOne   Pattern\n\tMatchAny   []Pattern\n}\n\nfunc (r HeaderRoute) IsMatch(value string) bool {\n\tif len(r.MatchAny) > 0 {\n\t\tfor _, m := range r.MatchAny {\n\t\t\tif m.Match(value) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t} else if r.MatchOne.Match(value) {\n\t\treturn true\n\t}\n\treturn false\n}\n\ntype Pattern struct {\n\tprefix   string\n\tsuffix   string\n\twildcard bool\n}\n\nfunc NewPattern(value string) Pattern {\n\tp := Pattern{}\n\tp.prefix, p.suffix, p.wildcard = strings.Cut(value, \"*\")\n\treturn p\n}\n\nfunc (p Pattern) Match(v string) bool {\n\tif !p.wildcard {\n\t\treturn p.prefix == v\n\t}\n\treturn len(v) >= len(p.prefix+p.suffix) && strings.HasPrefix(v, p.prefix) && strings.HasSuffix(v, p.suffix)\n}\n"
  },
  {
    "path": "middleware/route_headers_test.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"sync/atomic\"\n\t\"testing\"\n)\n\nfunc TestRouteHeaders(t *testing.T) {\n\tt.Run(\"empty router should call next handler exactly once\", func(t *testing.T) {\n\t\tvar callCount atomic.Int32\n\n\t\thr := RouteHeaders()\n\n\t\thandler := hr.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tcallCount.Add(1)\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t}))\n\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\t\trec := httptest.NewRecorder()\n\n\t\thandler.ServeHTTP(rec, req)\n\n\t\tif callCount.Load() != 1 {\n\t\t\tt.Errorf(\"expected next handler to be called exactly once, but was called %d times\", callCount.Load())\n\t\t}\n\t})\n\n\tt.Run(\"matching header should route to correct middleware\", func(t *testing.T) {\n\t\tvar matchedRoute string\n\n\t\thr := RouteHeaders().\n\t\t\tRoute(\"Host\", \"example.com\", func(next http.Handler) http.Handler {\n\t\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tmatchedRoute = \"example.com\"\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t})\n\t\t\t}).\n\t\t\tRoute(\"Host\", \"other.com\", func(next http.Handler) http.Handler {\n\t\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tmatchedRoute = \"other.com\"\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t})\n\t\t\t})\n\n\t\thandler := hr.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t}))\n\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\t\treq.Host = \"example.com\"\n\t\treq.Header.Set(\"Host\", \"example.com\")\n\t\trec := httptest.NewRecorder()\n\n\t\thandler.ServeHTTP(rec, req)\n\n\t\tif matchedRoute != \"example.com\" {\n\t\t\tt.Errorf(\"expected matched route to be 'example.com', got '%s'\", matchedRoute)\n\t\t}\n\t})\n\n\tt.Run(\"wildcard pattern should match\", func(t *testing.T) {\n\t\tvar matched bool\n\n\t\thr := RouteHeaders().\n\t\t\tRoute(\"Host\", \"*.example.com\", func(next http.Handler) http.Handler {\n\t\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tmatched = true\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t})\n\t\t\t})\n\n\t\thandler := hr.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t}))\n\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\t\treq.Header.Set(\"Host\", \"api.example.com\")\n\t\trec := httptest.NewRecorder()\n\n\t\thandler.ServeHTTP(rec, req)\n\n\t\tif !matched {\n\t\t\tt.Error(\"expected wildcard pattern to match\")\n\t\t}\n\t})\n\n\tt.Run(\"default route should be used when no match\", func(t *testing.T) {\n\t\tvar usedDefault bool\n\n\t\thr := RouteHeaders().\n\t\t\tRoute(\"Host\", \"example.com\", func(next http.Handler) http.Handler {\n\t\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t})\n\t\t\t}).\n\t\t\tRouteDefault(func(next http.Handler) http.Handler {\n\t\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tusedDefault = true\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t})\n\t\t\t})\n\n\t\thandler := hr.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t}))\n\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\t\treq.Header.Set(\"Host\", \"other.com\")\n\t\trec := httptest.NewRecorder()\n\n\t\thandler.ServeHTTP(rec, req)\n\n\t\tif !usedDefault {\n\t\t\tt.Error(\"expected default route to be used when no match\")\n\t\t}\n\t})\n\n\tt.Run(\"RouteAny should match any of the provided patterns\", func(t *testing.T) {\n\t\tvar matched bool\n\n\t\thr := RouteHeaders().\n\t\t\tRouteAny(\"Content-Type\", []string{\"application/json\", \"application/xml\"}, func(next http.Handler) http.Handler {\n\t\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tmatched = true\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t})\n\t\t\t})\n\n\t\thandler := hr.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t}))\n\n\t\t// Test with application/json\n\t\treq := httptest.NewRequest(\"POST\", \"/\", nil)\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t\trec := httptest.NewRecorder()\n\n\t\thandler.ServeHTTP(rec, req)\n\n\t\tif !matched {\n\t\t\tt.Error(\"expected RouteAny to match 'application/json'\")\n\t\t}\n\n\t\t// Reset and test with application/xml\n\t\tmatched = false\n\t\treq = httptest.NewRequest(\"POST\", \"/\", nil)\n\t\treq.Header.Set(\"Content-Type\", \"application/xml\")\n\t\trec = httptest.NewRecorder()\n\n\t\thandler.ServeHTTP(rec, req)\n\n\t\tif !matched {\n\t\t\tt.Error(\"expected RouteAny to match 'application/xml'\")\n\t\t}\n\t})\n\n\tt.Run(\"no match and no default should call next handler\", func(t *testing.T) {\n\t\tvar nextCalled bool\n\n\t\thr := RouteHeaders().\n\t\t\tRoute(\"Host\", \"example.com\", func(next http.Handler) http.Handler {\n\t\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t})\n\t\t\t})\n\n\t\thandler := hr.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tnextCalled = true\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t}))\n\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\t\treq.Header.Set(\"Host\", \"other.com\")\n\t\trec := httptest.NewRecorder()\n\n\t\thandler.ServeHTTP(rec, req)\n\n\t\tif !nextCalled {\n\t\t\tt.Error(\"expected next handler to be called when no match and no default\")\n\t\t}\n\t})\n}\n\nfunc TestPattern(t *testing.T) {\n\ttests := []struct {\n\t\tpattern  string\n\t\tvalue    string\n\t\texpected bool\n\t}{\n\t\t{\"example.com\", \"example.com\", true},\n\t\t{\"example.com\", \"other.com\", false},\n\t\t{\"*.example.com\", \"api.example.com\", true},\n\t\t{\"*.example.com\", \"example.com\", false},\n\t\t{\"api.*\", \"api.example.com\", true},\n\t\t{\"*\", \"anything\", true},\n\t\t{\"prefix*suffix\", \"prefixmiddlesuffix\", true},\n\t\t{\"prefix*suffix\", \"prefixsuffix\", true},\n\t\t{\"prefix*suffix\", \"wrongmiddlesuffix\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.pattern+\"_\"+tt.value, func(t *testing.T) {\n\t\t\tp := NewPattern(tt.pattern)\n\t\t\tif got := p.Match(tt.value); got != tt.expected {\n\t\t\t\tt.Errorf(\"Pattern(%q).Match(%q) = %v, want %v\", tt.pattern, tt.value, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/strip.go",
    "content": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\n// StripSlashes is a middleware that will match request paths with a trailing\n// slash, strip it from the path and continue routing through the mux, if a route\n// matches, then it will serve the handler.\nfunc StripSlashes(next http.Handler) http.Handler {\n\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\tvar path string\n\t\trctx := chi.RouteContext(r.Context())\n\t\tif rctx != nil && rctx.RoutePath != \"\" {\n\t\t\tpath = rctx.RoutePath\n\t\t} else {\n\t\t\tpath = r.URL.Path\n\t\t}\n\t\tif len(path) > 1 && path[len(path)-1] == '/' {\n\t\t\tnewPath := path[:len(path)-1]\n\t\t\tif rctx == nil {\n\t\t\t\tr.URL.Path = newPath\n\t\t\t} else {\n\t\t\t\trctx.RoutePath = newPath\n\t\t\t}\n\t\t}\n\t\tnext.ServeHTTP(w, r)\n\t}\n\treturn http.HandlerFunc(fn)\n}\n\n// RedirectSlashes is a middleware that will match request paths with a trailing\n// slash and redirect to the same path, less the trailing slash.\n//\n// NOTE: RedirectSlashes middleware is *incompatible* with http.FileServer,\n// see https://github.com/go-chi/chi/issues/343\nfunc RedirectSlashes(next http.Handler) http.Handler {\n\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\tvar path string\n\t\trctx := chi.RouteContext(r.Context())\n\t\tif rctx != nil && rctx.RoutePath != \"\" {\n\t\t\tpath = rctx.RoutePath\n\t\t} else {\n\t\t\tpath = r.URL.Path\n\t\t}\n\n\t\tif len(path) > 1 && path[len(path)-1] == '/' {\n\t\t\t// Normalize backslashes to forward slashes to prevent \"/\\evil.com\" style redirects\n\t\t\t// that some clients may interpret as protocol-relative.\n\t\t\tpath = strings.ReplaceAll(path, `\\`, `/`)\n\n\t\t\t// Collapse leading/trailing slashes and force a single leading slash.\n\t\t\tpath := \"/\" + strings.Trim(path, \"/\")\n\n\t\t\tif r.URL.RawQuery != \"\" {\n\t\t\t\tpath = fmt.Sprintf(\"%s?%s\", path, r.URL.RawQuery)\n\t\t\t}\n\t\t\thttp.Redirect(w, r, path, 301)\n\t\t\treturn\n\t\t}\n\n\t\tnext.ServeHTTP(w, r)\n\t}\n\treturn http.HandlerFunc(fn)\n}\n\n// StripPrefix is a middleware that will strip the provided prefix from the\n// request path before handing the request over to the next handler.\nfunc StripPrefix(prefix string) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.StripPrefix(prefix, next)\n\t}\n}\n"
  },
  {
    "path": "middleware/strip_test.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestStripSlashes(t *testing.T) {\n\tr := chi.NewRouter()\n\n\t// This middleware must be mounted at the top level of the router, not at the end-handler\n\t// because then it'll be too late and will end up in a 404\n\tr.Use(StripSlashes)\n\n\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(\"nothing here\"))\n\t})\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"root\"))\n\t})\n\n\tr.Route(\"/accounts/{accountID}\", func(r chi.Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\taccountID := chi.URLParam(r, \"accountID\")\n\t\t\tw.Write([]byte(accountID))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, resp := testRequest(t, ts, \"GET\", \"/\", nil); resp != \"root\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"//\", nil); resp != \"root\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/admin\", nil); resp != \"admin\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/admin/\", nil); resp != \"admin\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/nothing-here\", nil); resp != \"nothing here\" {\n\t\tt.Fatal(resp)\n\t}\n}\n\nfunc TestStripSlashesInRoute(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(\"nothing here\"))\n\t})\n\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hi\"))\n\t})\n\n\tr.Route(\"/accounts/{accountID}\", func(r chi.Router) {\n\t\tr.Use(StripSlashes)\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"accounts index\"))\n\t\t})\n\t\tr.Get(\"/query\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\taccountID := chi.URLParam(r, \"accountID\")\n\t\t\tw.Write([]byte(accountID))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, resp := testRequest(t, ts, \"GET\", \"/hi\", nil); resp != \"hi\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/hi/\", nil); resp != \"nothing here\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/admin\", nil); resp != \"accounts index\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/admin/\", nil); resp != \"accounts index\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/admin/query\", nil); resp != \"admin\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/admin/query/\", nil); resp != \"admin\" {\n\t\tt.Fatal(resp)\n\t}\n}\n\nfunc TestRedirectSlashes(t *testing.T) {\n\tr := chi.NewRouter()\n\n\t// This middleware must be mounted at the top level of the router, not at the end-handler\n\t// because then it'll be too late and will end up in a 404\n\tr.Use(RedirectSlashes)\n\n\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(\"nothing here\"))\n\t})\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"root\"))\n\t})\n\n\tr.Route(\"/accounts/{accountID}\", func(r chi.Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\taccountID := chi.URLParam(r, \"accountID\")\n\t\t\tw.Write([]byte(accountID))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif resp, body := testRequest(t, ts, \"GET\", \"/\", nil); body != \"root\" || resp.StatusCode != 200 {\n\t\tt.Fatal(body, resp.StatusCode)\n\t}\n\n\t// NOTE: the testRequest client will follow the redirection..\n\tif resp, body := testRequest(t, ts, \"GET\", \"//\", nil); body != \"root\" || resp.StatusCode != 200 {\n\t\tt.Fatal(body, resp.StatusCode)\n\t}\n\n\tif resp, body := testRequest(t, ts, \"GET\", \"/accounts/admin\", nil); body != \"admin\" || resp.StatusCode != 200 {\n\t\tt.Fatal(body, resp.StatusCode)\n\t}\n\n\t// NOTE: the testRequest client will follow the redirection..\n\tif resp, body := testRequest(t, ts, \"GET\", \"/accounts/admin/\", nil); body != \"admin\" || resp.StatusCode != 200 {\n\t\tt.Fatal(body, resp.StatusCode)\n\t}\n\n\tif resp, body := testRequest(t, ts, \"GET\", \"/nothing-here\", nil); body != \"nothing here\" || resp.StatusCode != 404 {\n\t\tt.Fatal(body, resp.StatusCode)\n\t}\n\n\t// Ensure redirect Location url is correct\n\t{\n\t\tresp, body := testRequestNoRedirect(t, ts, \"GET\", \"/accounts/someuser/\", nil)\n\t\tif resp.StatusCode != 301 {\n\t\t\tt.Fatal(body, resp.StatusCode)\n\t\t}\n\t\tlocation := resp.Header.Get(\"Location\")\n\t\tif location != \"/accounts/someuser\" {\n\t\t\tt.Fatalf(\"invalid redirection, should be /accounts/someuser\")\n\t\t}\n\t}\n\n\t// Ensure query params are kept in tact upon redirecting a slash\n\t{\n\t\tresp, body := testRequestNoRedirect(t, ts, \"GET\", \"/accounts/someuser/?a=1&b=2\", nil)\n\t\tif resp.StatusCode != 301 {\n\t\t\tt.Fatal(body, resp.StatusCode)\n\t\t}\n\t\tlocation := resp.Header.Get(\"Location\")\n\t\tif location != \"/accounts/someuser?a=1&b=2\" {\n\t\t\tt.Fatalf(\"invalid redirection, should be /accounts/someuser?a=1&b=2\")\n\t\t}\n\t}\n\n\t// Ensure that we don't redirect to 'evil.com', but rather to 'server.url/evil.com/'\n\t{\n\t\tpaths := []string{\"//evil.com/\", \"///evil.com/\"}\n\n\t\tfor _, p := range paths {\n\t\t\tresp, body := testRequest(t, ts, \"GET\", p, nil)\n\t\t\tif u, err := url.Parse(ts.URL); err != nil && resp.Request.URL.Host != u.Host {\n\t\t\t\tt.Fatalf(\"host should remain the same. got: %q, want: %q\", resp.Request.URL.Host, ts.URL)\n\t\t\t}\n\t\t\tif body != \"nothing here\" || resp.StatusCode != 404 {\n\t\t\t\tt.Fatal(body, resp.StatusCode)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure that we don't redirect to 'evil.com', but rather to 'server.url/evil.com/'\n\t{\n\t\tresp, body := testRequest(t, ts, \"GET\", \"//evil.com/\", nil)\n\t\tif u, err := url.Parse(ts.URL); err != nil && resp.Request.URL.Host != u.Host {\n\t\t\tt.Fatalf(\"host should remain the same. got: %q, want: %q\", resp.Request.URL.Host, ts.URL)\n\t\t}\n\t\tif body != \"nothing here\" || resp.StatusCode != 404 {\n\t\t\tt.Fatal(body, resp.StatusCode)\n\t\t}\n\t}\n}\n\n// This tests a http.Handler that is not chi.Router\n// In these cases, the routeContext is nil\nfunc TestStripSlashesWithNilContext(t *testing.T) {\n\tr := http.NewServeMux()\n\n\tr.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"root\"))\n\t})\n\n\tr.HandleFunc(\"/accounts\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"accounts\"))\n\t})\n\n\tr.HandleFunc(\"/accounts/admin\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"admin\"))\n\t})\n\n\tts := httptest.NewServer(StripSlashes(r))\n\tdefer ts.Close()\n\n\tif _, resp := testRequest(t, ts, \"GET\", \"/\", nil); resp != \"root\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"//\", nil); resp != \"root\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts\", nil); resp != \"accounts\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/\", nil); resp != \"accounts\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/admin\", nil); resp != \"admin\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/accounts/admin/\", nil); resp != \"admin\" {\n\t\tt.Fatal(resp)\n\t}\n}\n\nfunc TestStripPrefix(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.Use(StripPrefix(\"/api\"))\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"api root\"))\n\t})\n\n\tr.Get(\"/accounts\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"api accounts\"))\n\t})\n\n\tr.Get(\"/accounts/{accountID}\", func(w http.ResponseWriter, r *http.Request) {\n\t\taccountID := chi.URLParam(r, \"accountID\")\n\t\tw.Write([]byte(accountID))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, resp := testRequest(t, ts, \"GET\", \"/api/\", nil); resp != \"api root\" {\n\t\tt.Fatalf(\"got: %q, want: %q\", resp, \"api root\")\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/api/accounts\", nil); resp != \"api accounts\" {\n\t\tt.Fatalf(\"got: %q, want: %q\", resp, \"api accounts\")\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/api/accounts/admin\", nil); resp != \"admin\" {\n\t\tt.Fatalf(\"got: %q, want: %q\", resp, \"admin\")\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/api-nope/\", nil); resp != \"404 page not found\\n\" {\n\t\tt.Fatalf(\"got: %q, want: %q\", resp, \"404 page not found\\n\")\n\t}\n}\n\nfunc TestRedirectSlashes_PreventBackslashRelativeOpenRedirect(t *testing.T) {\n\th := RedirectSlashes(http.NotFoundHandler())\n\n\ttests := []struct {\n\t\tname   string\n\t\ttarget string\n\t}{\n\t\t{\n\t\t\tname:   `raw backslash: /\\evil.com/`,\n\t\t\ttarget: `/\\evil.com/`,\n\t\t},\n\t\t{\n\t\t\tname:   `encoded backslash: /%5Cevil.com/`,\n\t\t\ttarget: \"/%5Cevil.com/\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\treq := httptest.NewRequest(http.MethodGet, \"http://example.test\"+tc.target, nil)\n\t\t\trr := httptest.NewRecorder()\n\n\t\t\th.ServeHTTP(rr, req)\n\t\t\tres := rr.Result()\n\t\t\tdefer res.Body.Close()\n\n\t\t\tif res.StatusCode != http.StatusMovedPermanently {\n\t\t\t\tt.Fatalf(\"expected %d, got %d\", http.StatusMovedPermanently, res.StatusCode)\n\t\t\t}\n\n\t\t\tloc := res.Header.Get(\"Location\")\n\t\t\tif loc == \"\" {\n\t\t\t\tt.Fatalf(\"expected Location header to be set\")\n\t\t\t}\n\n\t\t\t// The core security assertions:\n\t\t\tif strings.Contains(loc, `\\`) {\n\t\t\t\tt.Fatalf(\"Location must not contain backslashes: %q\", loc)\n\t\t\t}\n\t\t\tif strings.HasPrefix(loc, \"//\") {\n\t\t\t\tt.Fatalf(\"Location must not be protocol-relative: %q\", loc)\n\t\t\t}\n\t\t\tif !strings.HasPrefix(loc, \"/\") {\n\t\t\t\tt.Fatalf(\"Location must be an absolute-path reference starting with '/': %q\", loc)\n\t\t\t}\n\n\t\t\t// Optional stronger assertion if your middleware normalizes to /evil.com exactly:\n\t\t\t// (Keep or remove depending on your chosen behavior.)\n\t\t\tif loc != \"/evil.com\" {\n\t\t\t\tt.Fatalf(\"expected Location %q, got %q\", \"/evil.com\", loc)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/sunset.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"time\"\n)\n\n// Sunset set Deprecation/Sunset header to response\n// This can be used to enable Sunset in a route or a route group\n// For more: https://www.rfc-editor.org/rfc/rfc8594.html\nfunc Sunset(sunsetAt time.Time, links ...string) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif !sunsetAt.IsZero() {\n\t\t\t\tw.Header().Set(\"Sunset\", sunsetAt.Format(http.TimeFormat))\n\t\t\t\tw.Header().Set(\"Deprecation\", sunsetAt.Format(http.TimeFormat))\n\n\t\t\t\tfor _, link := range links {\n\t\t\t\t\tw.Header().Add(\"Link\", link)\n\t\t\t\t}\n\t\t\t}\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/sunset_test.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestSunset(t *testing.T) {\n\n\tt.Run(\"Sunset without link\", func(t *testing.T) {\n\t\treq, _ := http.NewRequest(\"GET\", \"/\", nil)\n\t\tw := httptest.NewRecorder()\n\n\t\tr := chi.NewRouter()\n\n\t\tsunsetAt := time.Date(2025, 12, 24, 10, 20, 0, 0, time.UTC)\n\t\tr.Use(Sunset(sunsetAt))\n\n\t\tvar sunset, deprecation string\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tclonedHeader := w.Header().Clone()\n\t\t\tsunset = clonedHeader.Get(\"Sunset\")\n\t\t\tdeprecation = clonedHeader.Get(\"Deprecation\")\n\t\t\tw.Write([]byte(\"I'll be unavailable soon\"))\n\t\t})\n\t\tr.ServeHTTP(w, req)\n\n\t\tif w.Code != 200 {\n\t\t\tt.Fatal(\"Response Code should be 200\")\n\t\t}\n\n\t\tif sunset != \"Wed, 24 Dec 2025 10:20:00 GMT\" {\n\t\t\tt.Fatal(\"Test get sunset error.\", sunset)\n\t\t}\n\n\t\tif deprecation != \"Wed, 24 Dec 2025 10:20:00 GMT\" {\n\t\t\tt.Fatal(\"Test get deprecation error.\")\n\t\t}\n\t})\n\n\tt.Run(\"Sunset with link\", func(t *testing.T) {\n\t\treq, _ := http.NewRequest(\"GET\", \"/\", nil)\n\t\tw := httptest.NewRecorder()\n\n\t\tr := chi.NewRouter()\n\n\t\tsunsetAt := time.Date(2025, 12, 24, 10, 20, 0, 0, time.UTC)\n\t\tdeprecationLink := \"https://example.com/v1/deprecation-details\"\n\t\tr.Use(Sunset(sunsetAt, deprecationLink))\n\n\t\tvar sunset, deprecation, link string\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tclonedHeader := w.Header().Clone()\n\t\t\tsunset = clonedHeader.Get(\"Sunset\")\n\t\t\tdeprecation = clonedHeader.Get(\"Deprecation\")\n\t\t\tlink = clonedHeader.Get(\"Link\")\n\n\t\t\tw.Write([]byte(\"I'll be unavailable soon\"))\n\t\t})\n\n\t\tr.ServeHTTP(w, req)\n\n\t\tif w.Code != 200 {\n\t\t\tt.Fatal(\"Response Code should be 200\")\n\t\t}\n\n\t\tif sunset != \"Wed, 24 Dec 2025 10:20:00 GMT\" {\n\t\t\tt.Fatal(\"Test get sunset error.\", sunset)\n\t\t}\n\n\t\tif deprecation != \"Wed, 24 Dec 2025 10:20:00 GMT\" {\n\t\t\tt.Fatal(\"Test get deprecation error.\")\n\t\t}\n\n\t\tif link != deprecationLink {\n\t\t\tt.Fatal(\"Test get deprecation link error.\")\n\t\t}\n\t})\n\n}\n\n/**\nEXAMPLE USAGES\nfunc main() {\n\tr := chi.NewRouter()\n\n\tsunsetAt := time.Date(2025, 12, 24, 10, 20, 0, 0, time.UTC)\n\tr.Use(middleware.Sunset(sunsetAt))\n\n\t// can provide additional link for updated resource\n\t// r.Use(middleware.Sunset(sunsetAt, \"https://example.com/v1/deprecation-details\"))\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"This endpoint will be removed soon\"))\n\t})\n\n\tlog.Println(\"Listening on port: 3000\")\n\thttp.ListenAndServe(\":3000\", r)\n}\n**/\n"
  },
  {
    "path": "middleware/supress_notfound.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\n// SupressNotFound will quickly respond with a 404 if the route is not found\n// and will not continue to the next middleware handler.\n//\n// This is handy to put at the top of your middleware stack to avoid unnecessary\n// processing of requests that are not going to match any routes anyway. For\n// example its super annoying to see a bunch of 404's in your logs from bots.\nfunc SupressNotFound(router *chi.Mux) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\trctx := chi.RouteContext(r.Context())\n\t\t\tmatch := rctx.Routes.Match(rctx, r.Method, r.URL.Path)\n\t\t\tif !match {\n\t\t\t\trouter.NotFoundHandler().ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "middleware/terminal.go",
    "content": "package middleware\n\n// Ported from Goji's middleware, source:\n// https://github.com/zenazn/goji/tree/master/web/middleware\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n)\n\nvar (\n\t// Normal colors\n\tnBlack   = []byte{'\\033', '[', '3', '0', 'm'}\n\tnRed     = []byte{'\\033', '[', '3', '1', 'm'}\n\tnGreen   = []byte{'\\033', '[', '3', '2', 'm'}\n\tnYellow  = []byte{'\\033', '[', '3', '3', 'm'}\n\tnBlue    = []byte{'\\033', '[', '3', '4', 'm'}\n\tnMagenta = []byte{'\\033', '[', '3', '5', 'm'}\n\tnCyan    = []byte{'\\033', '[', '3', '6', 'm'}\n\tnWhite   = []byte{'\\033', '[', '3', '7', 'm'}\n\t// Bright colors\n\tbBlack   = []byte{'\\033', '[', '3', '0', ';', '1', 'm'}\n\tbRed     = []byte{'\\033', '[', '3', '1', ';', '1', 'm'}\n\tbGreen   = []byte{'\\033', '[', '3', '2', ';', '1', 'm'}\n\tbYellow  = []byte{'\\033', '[', '3', '3', ';', '1', 'm'}\n\tbBlue    = []byte{'\\033', '[', '3', '4', ';', '1', 'm'}\n\tbMagenta = []byte{'\\033', '[', '3', '5', ';', '1', 'm'}\n\tbCyan    = []byte{'\\033', '[', '3', '6', ';', '1', 'm'}\n\tbWhite   = []byte{'\\033', '[', '3', '7', ';', '1', 'm'}\n\n\treset = []byte{'\\033', '[', '0', 'm'}\n)\n\nvar IsTTY bool\n\nfunc init() {\n\t// This is sort of cheating: if stdout is a character device, we assume\n\t// that means it's a TTY. Unfortunately, there are many non-TTY\n\t// character devices, but fortunately stdout is rarely set to any of\n\t// them.\n\t//\n\t// We could solve this properly by pulling in a dependency on\n\t// code.google.com/p/go.crypto/ssh/terminal, for instance, but as a\n\t// heuristic for whether to print in color or in black-and-white, I'd\n\t// really rather not.\n\tfi, err := os.Stdout.Stat()\n\tif err == nil {\n\t\tm := os.ModeDevice | os.ModeCharDevice\n\t\tIsTTY = fi.Mode()&m == m\n\t}\n}\n\n// colorWrite\nfunc cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) {\n\tif IsTTY && useColor {\n\t\tw.Write(color)\n\t}\n\tfmt.Fprintf(w, s, args...)\n\tif IsTTY && useColor {\n\t\tw.Write(reset)\n\t}\n}\n"
  },
  {
    "path": "middleware/throttle.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n)\n\nconst (\n\terrCapacityExceeded = \"Server capacity exceeded.\"\n\terrTimedOut         = \"Timed out while waiting for a pending request to complete.\"\n\terrContextCanceled  = \"Context was canceled.\"\n)\n\nvar (\n\tdefaultBacklogTimeout = time.Second * 60\n)\n\n// ThrottleOpts represents a set of throttling options.\ntype ThrottleOpts struct {\n\tRetryAfterFn   func(ctxDone bool) time.Duration\n\tLimit          int\n\tBacklogLimit   int\n\tBacklogTimeout time.Duration\n\tStatusCode     int\n}\n\n// Throttle is a middleware that limits number of currently processed requests\n// at a time across all users. Note: Throttle is not a rate-limiter per user,\n// instead it just puts a ceiling on the number of current in-flight requests\n// being processed from the point from where the Throttle middleware is mounted.\nfunc Throttle(limit int) func(http.Handler) http.Handler {\n\treturn ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogTimeout: defaultBacklogTimeout})\n}\n\n// ThrottleBacklog is a middleware that limits number of currently processed\n// requests at a time and provides a backlog for holding a finite number of\n// pending requests.\nfunc ThrottleBacklog(limit, backlogLimit int, backlogTimeout time.Duration) func(http.Handler) http.Handler {\n\treturn ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogLimit: backlogLimit, BacklogTimeout: backlogTimeout})\n}\n\n// ThrottleWithOpts is a middleware that limits number of currently processed requests using passed ThrottleOpts.\nfunc ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler {\n\tif opts.Limit < 1 {\n\t\tpanic(\"chi/middleware: Throttle expects limit > 0\")\n\t}\n\n\tif opts.BacklogLimit < 0 {\n\t\tpanic(\"chi/middleware: Throttle expects backlogLimit to be positive\")\n\t}\n\n\tstatusCode := opts.StatusCode\n\tif statusCode == 0 {\n\t\tstatusCode = http.StatusTooManyRequests\n\t}\n\n\tt := throttler{\n\t\ttokens:         make(chan token, opts.Limit),\n\t\tbacklogTokens:  make(chan token, opts.Limit+opts.BacklogLimit),\n\t\tbacklogTimeout: opts.BacklogTimeout,\n\t\tstatusCode:     statusCode,\n\t\tretryAfterFn:   opts.RetryAfterFn,\n\t}\n\n\t// Filling tokens.\n\tfor i := 0; i < opts.Limit+opts.BacklogLimit; i++ {\n\t\tif i < opts.Limit {\n\t\t\tt.tokens <- token{}\n\t\t}\n\t\tt.backlogTokens <- token{}\n\t}\n\n\treturn func(next http.Handler) http.Handler {\n\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\n\t\t\tselect {\n\n\t\t\tcase <-ctx.Done():\n\t\t\t\tt.setRetryAfterHeaderIfNeeded(w, true)\n\t\t\t\thttp.Error(w, errContextCanceled, t.statusCode)\n\t\t\t\treturn\n\n\t\t\tcase btok := <-t.backlogTokens:\n\t\t\t\tdefer func() {\n\t\t\t\t\tt.backlogTokens <- btok\n\t\t\t\t}()\n\n\t\t\t\t// Try to get a processing token immediately first\n\t\t\t\tselect {\n\t\t\t\tcase tok := <-t.tokens:\n\t\t\t\t\tdefer func() {\n\t\t\t\t\t\tt.tokens <- tok\n\t\t\t\t\t}()\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t\t// No immediate token available, need to wait with timer\n\t\t\t\t}\n\n\t\t\t\ttimer := time.NewTimer(t.backlogTimeout)\n\t\t\t\tselect {\n\t\t\t\tcase <-timer.C:\n\t\t\t\t\tt.setRetryAfterHeaderIfNeeded(w, false)\n\t\t\t\t\thttp.Error(w, errTimedOut, t.statusCode)\n\t\t\t\t\treturn\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\ttimer.Stop()\n\t\t\t\t\tt.setRetryAfterHeaderIfNeeded(w, true)\n\t\t\t\t\thttp.Error(w, errContextCanceled, t.statusCode)\n\t\t\t\t\treturn\n\t\t\t\tcase tok := <-t.tokens:\n\t\t\t\t\tdefer func() {\n\t\t\t\t\t\ttimer.Stop()\n\t\t\t\t\t\tt.tokens <- tok\n\t\t\t\t\t}()\n\t\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\t}\n\t\t\t\treturn\n\n\t\t\tdefault:\n\t\t\t\tt.setRetryAfterHeaderIfNeeded(w, false)\n\t\t\t\thttp.Error(w, errCapacityExceeded, t.statusCode)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn http.HandlerFunc(fn)\n\t}\n}\n\n// token represents a request that is being processed.\ntype token struct{}\n\n// throttler limits number of currently processed requests at a time.\ntype throttler struct {\n\ttokens         chan token\n\tbacklogTokens  chan token\n\tretryAfterFn   func(ctxDone bool) time.Duration\n\tbacklogTimeout time.Duration\n\tstatusCode     int\n}\n\n// setRetryAfterHeaderIfNeeded sets Retry-After HTTP header if corresponding retryAfterFn option of throttler is initialized.\nfunc (t throttler) setRetryAfterHeaderIfNeeded(w http.ResponseWriter, ctxDone bool) {\n\tif t.retryAfterFn == nil {\n\t\treturn\n\t}\n\tw.Header().Set(\"Retry-After\", strconv.Itoa(int(t.retryAfterFn(ctxDone).Seconds())))\n}\n"
  },
  {
    "path": "middleware/throttle_test.go",
    "content": "package middleware\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nvar testContent = []byte(\"Hello world!\")\n\nfunc TestThrottleBacklog(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.Use(ThrottleBacklog(10, 50, time.Second*10))\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\ttime.Sleep(time.Second * 1) // Expensive operation.\n\t\tw.Write(testContent)\n\t})\n\n\tserver := httptest.NewServer(r)\n\tdefer server.Close()\n\n\tclient := http.Client{\n\t\tTimeout: time.Second * 5, // Maximum waiting time.\n\t}\n\n\tvar wg sync.WaitGroup\n\n\t// The throttler processes 10 consecutive requests, each one of those\n\t// requests lasts 1s. The maximum number of requests this can possible serve\n\t// before the clients time out (5s) is 40.\n\tfor i := range 40 {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tres, err := client.Get(server.URL)\n\t\t\tassertNoError(t, err)\n\n\t\t\tassertEqual(t, http.StatusOK, res.StatusCode)\n\t\t\tbuf, err := io.ReadAll(res.Body)\n\t\t\tassertNoError(t, err)\n\t\t\tassertEqual(t, testContent, buf)\n\t\t}(i)\n\t}\n\n\twg.Wait()\n}\n\nfunc TestThrottleClientTimeout(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.Use(ThrottleBacklog(10, 50, time.Second*10))\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\ttime.Sleep(time.Second * 5) // Expensive operation.\n\t\tw.Write(testContent)\n\t})\n\n\tserver := httptest.NewServer(r)\n\tdefer server.Close()\n\n\tclient := http.Client{\n\t\tTimeout: time.Second * 3, // Maximum waiting time.\n\t}\n\n\tvar wg sync.WaitGroup\n\n\tfor i := range 10 {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\t_, err := client.Get(server.URL)\n\t\t\tassertError(t, err)\n\t\t}(i)\n\t}\n\n\twg.Wait()\n}\n\nfunc TestThrottleTriggerGatewayTimeout(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.Use(ThrottleBacklog(50, 100, time.Second*5))\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\ttime.Sleep(time.Second * 10) // Expensive operation.\n\t\tw.Write(testContent)\n\t})\n\n\tserver := httptest.NewServer(r)\n\tdefer server.Close()\n\n\tclient := http.Client{\n\t\tTimeout: time.Second * 60, // Maximum waiting time.\n\t}\n\n\tvar wg sync.WaitGroup\n\n\t// These requests will be processed normally until they finish.\n\tfor i := range 50 {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tres, err := client.Get(server.URL)\n\t\t\tassertNoError(t, err)\n\t\t\tassertEqual(t, http.StatusOK, res.StatusCode)\n\t\t}(i)\n\t}\n\n\ttime.Sleep(time.Second * 1)\n\n\t// These requests will wait for the first batch to complete but it will take\n\t// too much time, so they will eventually receive a timeout error.\n\tfor i := range 50 {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tres, err := client.Get(server.URL)\n\t\t\tassertNoError(t, err)\n\n\t\t\tbuf, err := io.ReadAll(res.Body)\n\t\t\tassertNoError(t, err)\n\t\t\tassertEqual(t, http.StatusTooManyRequests, res.StatusCode)\n\t\t\tassertEqual(t, errTimedOut, strings.TrimSpace(string(buf)))\n\t\t}(i)\n\t}\n\n\twg.Wait()\n}\n\nfunc TestThrottleMaximum(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.Use(ThrottleBacklog(10, 10, time.Second*5))\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\ttime.Sleep(time.Second * 3) // Expensive operation.\n\t\tw.Write(testContent)\n\t})\n\n\tserver := httptest.NewServer(r)\n\tdefer server.Close()\n\n\tclient := http.Client{\n\t\tTimeout: time.Second * 60, // Maximum waiting time.\n\t}\n\n\tvar wg sync.WaitGroup\n\n\tfor i := range 20 {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tres, err := client.Get(server.URL)\n\t\t\tassertNoError(t, err)\n\t\t\tassertEqual(t, http.StatusOK, res.StatusCode)\n\n\t\t\tbuf, err := io.ReadAll(res.Body)\n\t\t\tassertNoError(t, err)\n\t\t\tassertEqual(t, testContent, buf)\n\t\t}(i)\n\t}\n\n\t// Wait less time than what the server takes to reply.\n\ttime.Sleep(time.Second * 2)\n\n\t// At this point the server is still processing, all the following request\n\t// will be beyond the server capacity.\n\tfor i := range 20 {\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tres, err := client.Get(server.URL)\n\t\t\tassertNoError(t, err)\n\n\t\t\tbuf, err := io.ReadAll(res.Body)\n\t\t\tassertNoError(t, err)\n\t\t\tassertEqual(t, http.StatusTooManyRequests, res.StatusCode)\n\t\t\tassertEqual(t, errCapacityExceeded, strings.TrimSpace(string(buf)))\n\t\t}(i)\n\t}\n\n\twg.Wait()\n}\n\nfunc TestThrottleRetryAfter(t *testing.T) {\n\tr := chi.NewRouter()\n\tretryAfterFn := func(ctxDone bool) time.Duration { return time.Hour }\n\n\tr.Use(ThrottleWithOpts(ThrottleOpts{\n\t\tLimit:        5,\n\t\tBacklogLimit: 0,\n\t\tRetryAfterFn: retryAfterFn,\n\t}))\n\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\ttime.Sleep(time.Second * 1) // Expensive operation.\n\t\tw.WriteHeader(http.StatusOK)\n\t\tw.Write([]byte(\"ok\"))\n\t})\n\n\tserver := httptest.NewServer(r)\n\tdefer server.Close()\n\tclient := http.Client{}\n\n\ttype result struct {\n\t\tstatus int\n\t\theader http.Header\n\t}\n\n\tvar wg sync.WaitGroup\n\ttotalRequests := 10\n\tresultsCh := make(chan result, totalRequests)\n\n\tfor i := 0; i < totalRequests; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tres, _ := client.Get(server.URL)\n\t\t\tresultsCh <- result{status: res.StatusCode, header: res.Header}\n\t\t}()\n\t}\n\n\twg.Wait()\n\tclose(resultsCh)\n\n\tcount200 := 0\n\tcount429 := 0\n\tfor res := range resultsCh {\n\t\tswitch res.status {\n\t\tcase http.StatusOK:\n\t\t\tcount200++\n\t\t\tcontinue\n\t\tcase http.StatusTooManyRequests:\n\t\t\tcount429++\n\t\t\tassertEqual(t, \"3600\", res.header.Get(\"Retry-After\"))\n\t\t\tcontinue\n\t\tdefault:\n\t\t\tt.Fatalf(\"Unexpected status code: %d\", res.status)\n\t\t\tcontinue\n\t\t}\n\t}\n\n\tassertEqual(t, 5, count200)\n\tassertEqual(t, 5, count429)\n}\n\nfunc TestThrottleCustomStatusCode(t *testing.T) {\n\tconst timeout = time.Second * 3\n\n\twait := make(chan struct{})\n\n\tr := chi.NewRouter()\n\tr.Use(ThrottleWithOpts(ThrottleOpts{Limit: 1, StatusCode: http.StatusServiceUnavailable}))\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tselect {\n\t\tcase <-wait:\n\t\tcase <-time.After(timeout):\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tserver := httptest.NewServer(r)\n\tdefer server.Close()\n\n\tconst totalRequestCount = 5\n\n\tcodes := make(chan int, totalRequestCount)\n\terrs := make(chan error, totalRequestCount)\n\tclient := &http.Client{Timeout: timeout}\n\tfor range totalRequestCount {\n\t\tgo func() {\n\t\t\tresp, err := client.Get(server.URL)\n\t\t\tif err != nil {\n\t\t\t\terrs <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcodes <- resp.StatusCode\n\t\t}()\n\t}\n\n\twaitResponse := func(wantCode int) {\n\t\tselect {\n\t\tcase err := <-errs:\n\t\t\tt.Fatal(err)\n\t\tcase code := <-codes:\n\t\t\tassertEqual(t, wantCode, code)\n\t\tcase <-time.After(timeout):\n\t\t\tt.Fatalf(\"waiting %d code, timeout exceeded\", wantCode)\n\t\t}\n\t}\n\n\tfor range totalRequestCount - 1 {\n\t\twaitResponse(http.StatusServiceUnavailable)\n\t}\n\tclose(wait) // Allow the last request to proceed.\n\twaitResponse(http.StatusOK)\n}\n\nfunc BenchmarkThrottle(b *testing.B) {\n\tthrottleMiddleware := ThrottleBacklog(1000, 50, time.Second)\n\n\thandler := throttleMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\n\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\tb.ResetTimer()\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N; i++ {\n\t\tw := httptest.NewRecorder()\n\t\thandler.ServeHTTP(w, req)\n\t}\n}\n"
  },
  {
    "path": "middleware/timeout.go",
    "content": "package middleware\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"time\"\n)\n\n// Timeout is a middleware that cancels ctx after a given timeout and return\n// a 504 Gateway Timeout error to the client.\n//\n// It's required that you select the ctx.Done() channel to check for the signal\n// if the context has reached its deadline and return, otherwise the timeout\n// signal will be just ignored.\n//\n// ie. a route/handler may look like:\n//\n//\tr.Get(\"/long\", func(w http.ResponseWriter, r *http.Request) {\n//\t\tctx := r.Context()\n//\t\tprocessTime := time.Duration(rand.Intn(4)+1) * time.Second\n//\n//\t\tselect {\n//\t\tcase <-ctx.Done():\n//\t\t\treturn\n//\n//\t\tcase <-time.After(processTime):\n//\t\t\t// The above channel simulates some hard work.\n//\t\t}\n//\n//\t\tw.Write([]byte(\"done\"))\n//\t})\nfunc Timeout(timeout time.Duration) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx, cancel := context.WithTimeout(r.Context(), timeout)\n\t\t\tdefer func() {\n\t\t\t\tcancel()\n\t\t\t\tif ctx.Err() == context.DeadlineExceeded {\n\t\t\t\t\tw.WriteHeader(http.StatusGatewayTimeout)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tr = r.WithContext(ctx)\n\t\t\tnext.ServeHTTP(w, r)\n\t\t}\n\t\treturn http.HandlerFunc(fn)\n\t}\n}\n"
  },
  {
    "path": "middleware/url_format.go",
    "content": "package middleware\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nvar (\n\t// URLFormatCtxKey is the context.Context key to store the URL format data\n\t// for a request.\n\tURLFormatCtxKey = &contextKey{\"URLFormat\"}\n)\n\n// URLFormat is a middleware that parses the url extension from a request path and stores it\n// on the context as a string under the key `middleware.URLFormatCtxKey`. The middleware will\n// trim the suffix from the routing path and continue routing.\n//\n// Routers should not include a url parameter for the suffix when using this middleware.\n//\n// Sample usage for url paths `/articles/1`, `/articles/1.json` and `/articles/1.xml`:\n//\n//\tfunc routes() http.Handler {\n//\t\tr := chi.NewRouter()\n//\t\tr.Use(middleware.URLFormat)\n//\n//\t\tr.Get(\"/articles/{id}\", ListArticles)\n//\n//\t\treturn r\n//\t}\n//\n//\tfunc ListArticles(w http.ResponseWriter, r *http.Request) {\n//\t\turlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string)\n//\n//\t\tswitch urlFormat {\n//\t\tcase \"json\":\n//\t\t\trender.JSON(w, r, articles)\n//\t\tcase \"xml:\"\n//\t\t\trender.XML(w, r, articles)\n//\t\tdefault:\n//\t\t\trender.JSON(w, r, articles)\n//\t\t}\n//\t}\nfunc URLFormat(next http.Handler) http.Handler {\n\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\tctx := r.Context()\n\n\t\tvar format string\n\t\tpath := r.URL.Path\n\n\t\trctx := chi.RouteContext(r.Context())\n\t\tif rctx != nil && rctx.RoutePath != \"\" {\n\t\t\tpath = rctx.RoutePath\n\t\t}\n\n\t\tif strings.Index(path, \".\") > 0 {\n\t\t\tbase := strings.LastIndex(path, \"/\")\n\t\t\tidx := strings.LastIndex(path[base:], \".\")\n\n\t\t\tif idx > 0 {\n\t\t\t\tidx += base\n\t\t\t\tformat = path[idx+1:]\n\n\t\t\t\tif rctx != nil {\n\t\t\t\t\trctx.RoutePath = path[:idx]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tr = r.WithContext(context.WithValue(ctx, URLFormatCtxKey, format))\n\n\t\tnext.ServeHTTP(w, r)\n\t}\n\treturn http.HandlerFunc(fn)\n}\n"
  },
  {
    "path": "middleware/url_format_test.go",
    "content": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/go-chi/chi/v5\"\n)\n\nfunc TestURLFormat(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.Use(URLFormat)\n\n\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(\"nothing here\"))\n\t})\n\n\tr.Route(\"/samples/articles/samples.{articleID}\", func(r chi.Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tarticleID := chi.URLParam(r, \"articleID\")\n\t\t\tw.Write([]byte(articleID))\n\t\t})\n\t})\n\n\tr.Route(\"/articles/{articleID}\", func(r chi.Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tarticleID := chi.URLParam(r, \"articleID\")\n\t\t\tw.Write([]byte(articleID))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, resp := testRequest(t, ts, \"GET\", \"/articles/1.json\", nil); resp != \"1\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/articles/1.xml\", nil); resp != \"1\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/samples/articles/samples.1.json\", nil); resp != \"1\" {\n\t\tt.Fatal(resp)\n\t}\n\tif _, resp := testRequest(t, ts, \"GET\", \"/samples/articles/samples.1.xml\", nil); resp != \"1\" {\n\t\tt.Fatal(resp)\n\t}\n}\n\nfunc TestURLFormatInSubRouter(t *testing.T) {\n\tr := chi.NewRouter()\n\n\tr.Route(\"/articles/{articleID}\", func(r chi.Router) {\n\t\tr.Use(URLFormat)\n\t\tr.Get(\"/subroute\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tarticleID := chi.URLParam(r, \"articleID\")\n\t\t\tw.Write([]byte(articleID))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, resp := testRequest(t, ts, \"GET\", \"/articles/1/subroute.json\", nil); resp != \"1\" {\n\t\tt.Fatal(resp)\n\t}\n}\n"
  },
  {
    "path": "middleware/value.go",
    "content": "package middleware\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\n// WithValue is a middleware that sets a given key/value in a context chain.\nfunc WithValue(key, val interface{}) func(next http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\t\tr = r.WithContext(context.WithValue(r.Context(), key, val))\n\t\t\tnext.ServeHTTP(w, r)\n\t\t}\n\t\treturn http.HandlerFunc(fn)\n\t}\n}\n"
  },
  {
    "path": "middleware/wrap_writer.go",
    "content": "package middleware\n\n// The original work was derived from Goji's middleware, source:\n// https://github.com/zenazn/goji/tree/master/web/middleware\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n)\n\n// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to\n// hook into various parts of the response process.\nfunc NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter {\n\t_, fl := w.(http.Flusher)\n\n\tbw := basicWriter{ResponseWriter: w}\n\n\tif protoMajor == 2 {\n\t\t_, ps := w.(http.Pusher)\n\t\tif fl && ps {\n\t\t\treturn &http2FancyWriter{bw}\n\t\t}\n\t} else {\n\t\t_, hj := w.(http.Hijacker)\n\t\t_, rf := w.(io.ReaderFrom)\n\t\tif fl && hj && rf {\n\t\t\treturn &httpFancyWriter{bw}\n\t\t}\n\t\tif fl && hj {\n\t\t\treturn &flushHijackWriter{bw}\n\t\t}\n\t\tif hj {\n\t\t\treturn &hijackWriter{bw}\n\t\t}\n\t}\n\n\tif fl {\n\t\treturn &flushWriter{bw}\n\t}\n\n\treturn &bw\n}\n\n// WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook\n// into various parts of the response process.\ntype WrapResponseWriter interface {\n\thttp.ResponseWriter\n\t// Status returns the HTTP status of the request, or 0 if one has not\n\t// yet been sent.\n\tStatus() int\n\t// BytesWritten returns the total number of bytes sent to the client.\n\tBytesWritten() int\n\t// Tee causes the response body to be written to the given io.Writer in\n\t// addition to proxying the writes through. Only one io.Writer can be\n\t// tee'd to at once: setting a second one will overwrite the first.\n\t// Writes will be sent to the proxy before being written to this\n\t// io.Writer. It is illegal for the tee'd writer to be modified\n\t// concurrently with writes.\n\tTee(io.Writer)\n\t// Unwrap returns the original proxied target.\n\tUnwrap() http.ResponseWriter\n\t// Discard causes all writes to the original ResponseWriter be discarded,\n\t// instead writing only to the tee'd writer if it's set.\n\t// The caller is responsible for calling WriteHeader and Write on the\n\t// original ResponseWriter once the processing is done.\n\tDiscard()\n}\n\n// basicWriter wraps a http.ResponseWriter that implements the minimal\n// http.ResponseWriter interface.\ntype basicWriter struct {\n\thttp.ResponseWriter\n\ttee         io.Writer\n\tcode        int\n\tbytes       int\n\twroteHeader bool\n\tdiscard     bool\n}\n\nfunc (b *basicWriter) WriteHeader(code int) {\n\tif code >= 100 && code <= 199 && code != http.StatusSwitchingProtocols {\n\t\tif !b.discard {\n\t\t\tb.ResponseWriter.WriteHeader(code)\n\t\t}\n\t} else if !b.wroteHeader {\n\t\tb.code = code\n\t\tb.wroteHeader = true\n\t\tif !b.discard {\n\t\t\tb.ResponseWriter.WriteHeader(code)\n\t\t}\n\t}\n}\n\nfunc (b *basicWriter) Write(buf []byte) (n int, err error) {\n\tb.maybeWriteHeader()\n\tif !b.discard {\n\t\tn, err = b.ResponseWriter.Write(buf)\n\t\tif b.tee != nil {\n\t\t\t_, err2 := b.tee.Write(buf[:n])\n\t\t\t// Prefer errors generated by the proxied writer.\n\t\t\tif err == nil {\n\t\t\t\terr = err2\n\t\t\t}\n\t\t}\n\t} else if b.tee != nil {\n\t\tn, err = b.tee.Write(buf)\n\t} else {\n\t\tn, err = io.Discard.Write(buf)\n\t}\n\tb.bytes += n\n\treturn n, err\n}\n\nfunc (b *basicWriter) maybeWriteHeader() {\n\tif !b.wroteHeader {\n\t\tb.WriteHeader(http.StatusOK)\n\t}\n}\n\nfunc (b *basicWriter) Status() int {\n\treturn b.code\n}\n\nfunc (b *basicWriter) BytesWritten() int {\n\treturn b.bytes\n}\n\nfunc (b *basicWriter) Tee(w io.Writer) {\n\tb.tee = w\n}\n\nfunc (b *basicWriter) Unwrap() http.ResponseWriter {\n\treturn b.ResponseWriter\n}\n\nfunc (b *basicWriter) Discard() {\n\tb.discard = true\n}\n\n// flushWriter ...\ntype flushWriter struct {\n\tbasicWriter\n}\n\nfunc (f *flushWriter) Flush() {\n\tf.wroteHeader = true\n\tfl := f.basicWriter.ResponseWriter.(http.Flusher)\n\tfl.Flush()\n}\n\nvar _ http.Flusher = &flushWriter{}\n\n// hijackWriter ...\ntype hijackWriter struct {\n\tbasicWriter\n}\n\nfunc (f *hijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\thj := f.basicWriter.ResponseWriter.(http.Hijacker)\n\treturn hj.Hijack()\n}\n\nvar _ http.Hijacker = &hijackWriter{}\n\n// flushHijackWriter ...\ntype flushHijackWriter struct {\n\tbasicWriter\n}\n\nfunc (f *flushHijackWriter) Flush() {\n\tf.wroteHeader = true\n\tfl := f.basicWriter.ResponseWriter.(http.Flusher)\n\tfl.Flush()\n}\n\nfunc (f *flushHijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\thj := f.basicWriter.ResponseWriter.(http.Hijacker)\n\treturn hj.Hijack()\n}\n\nvar _ http.Flusher = &flushHijackWriter{}\nvar _ http.Hijacker = &flushHijackWriter{}\n\n// httpFancyWriter is a HTTP writer that additionally satisfies\n// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case\n// of wrapping the http.ResponseWriter that package http gives you, in order to\n// make the proxied object support the full method set of the proxied object.\ntype httpFancyWriter struct {\n\tbasicWriter\n}\n\nfunc (f *httpFancyWriter) Flush() {\n\tf.wroteHeader = true\n\tfl := f.basicWriter.ResponseWriter.(http.Flusher)\n\tfl.Flush()\n}\n\nfunc (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\thj := f.basicWriter.ResponseWriter.(http.Hijacker)\n\treturn hj.Hijack()\n}\n\nfunc (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {\n\treturn f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts)\n}\n\nfunc (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) {\n\tif f.basicWriter.tee != nil {\n\t\tn, err := io.Copy(&f.basicWriter, r)\n\t\tf.basicWriter.bytes += int(n)\n\t\treturn n, err\n\t}\n\trf := f.basicWriter.ResponseWriter.(io.ReaderFrom)\n\tf.basicWriter.maybeWriteHeader()\n\tn, err := rf.ReadFrom(r)\n\tf.basicWriter.bytes += int(n)\n\treturn n, err\n}\n\nvar _ http.Flusher = &httpFancyWriter{}\nvar _ http.Hijacker = &httpFancyWriter{}\nvar _ http.Pusher = &http2FancyWriter{}\nvar _ io.ReaderFrom = &httpFancyWriter{}\n\n// http2FancyWriter is a HTTP2 writer that additionally satisfies\n// http.Flusher, and io.ReaderFrom. It exists for the common case\n// of wrapping the http.ResponseWriter that package http gives you, in order to\n// make the proxied object support the full method set of the proxied object.\ntype http2FancyWriter struct {\n\tbasicWriter\n}\n\nfunc (f *http2FancyWriter) Flush() {\n\tf.wroteHeader = true\n\tfl := f.basicWriter.ResponseWriter.(http.Flusher)\n\tfl.Flush()\n}\n\nvar _ http.Flusher = &http2FancyWriter{}\n"
  },
  {
    "path": "middleware/wrap_writer_test.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestHttpFancyWriterRemembersWroteHeaderWhenFlushed(t *testing.T) {\n\tf := &httpFancyWriter{basicWriter: basicWriter{ResponseWriter: httptest.NewRecorder()}}\n\tf.Flush()\n\n\tif !f.wroteHeader {\n\t\tt.Fatal(\"want Flush to have set wroteHeader=true\")\n\t}\n}\n\nfunc TestHttp2FancyWriterRemembersWroteHeaderWhenFlushed(t *testing.T) {\n\tf := &http2FancyWriter{basicWriter{ResponseWriter: httptest.NewRecorder()}}\n\tf.Flush()\n\n\tif !f.wroteHeader {\n\t\tt.Fatal(\"want Flush to have set wroteHeader=true\")\n\t}\n}\n\nfunc TestBasicWritesTeesWritesWithoutDiscard(t *testing.T) {\n\t// explicitly create the struct instead of NewRecorder to control the value of Code\n\toriginal := &httptest.ResponseRecorder{\n\t\tHeaderMap: make(http.Header),\n\t\tBody:      new(bytes.Buffer),\n\t}\n\twrap := &basicWriter{ResponseWriter: original}\n\n\tvar buf bytes.Buffer\n\twrap.Tee(&buf)\n\n\t_, err := wrap.Write([]byte(\"hello world\"))\n\tassertNoError(t, err)\n\n\tassertEqual(t, 200, original.Code)\n\tassertEqual(t, []byte(\"hello world\"), original.Body.Bytes())\n\tassertEqual(t, []byte(\"hello world\"), buf.Bytes())\n\tassertEqual(t, 11, wrap.BytesWritten())\n}\n\nfunc TestBasicWriterDiscardsWritesToOriginalResponseWriter(t *testing.T) {\n\tt.Run(\"With Tee\", func(t *testing.T) {\n\t\t// explicitly create the struct instead of NewRecorder to control the value of Code\n\t\toriginal := &httptest.ResponseRecorder{\n\t\t\tHeaderMap: make(http.Header),\n\t\t\tBody:      new(bytes.Buffer),\n\t\t}\n\t\twrap := &basicWriter{ResponseWriter: original}\n\n\t\tvar buf bytes.Buffer\n\t\twrap.Tee(&buf)\n\t\twrap.Discard()\n\n\t\t_, err := wrap.Write([]byte(\"hello world\"))\n\t\tassertNoError(t, err)\n\n\t\tassertEqual(t, 0, original.Code) // wrapper shouldn't call WriteHeader implicitly\n\t\tassertEqual(t, 0, original.Body.Len())\n\t\tassertEqual(t, []byte(\"hello world\"), buf.Bytes())\n\t\tassertEqual(t, 11, wrap.BytesWritten())\n\t})\n\n\tt.Run(\"Without Tee\", func(t *testing.T) {\n\t\t// explicitly create the struct instead of NewRecorder to control the value of Code\n\t\toriginal := &httptest.ResponseRecorder{\n\t\t\tHeaderMap: make(http.Header),\n\t\t\tBody:      new(bytes.Buffer),\n\t\t}\n\t\twrap := &basicWriter{ResponseWriter: original}\n\t\twrap.Discard()\n\n\t\t_, err := wrap.Write([]byte(\"hello world\"))\n\t\tassertNoError(t, err)\n\n\t\tassertEqual(t, 0, original.Code) // wrapper shouldn't call WriteHeader implicitly\n\t\tassertEqual(t, 0, original.Body.Len())\n\t\tassertEqual(t, 11, wrap.BytesWritten())\n\t})\n}\n"
  },
  {
    "path": "mux.go",
    "content": "package chi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n)\n\nvar _ Router = &Mux{}\n\n// Mux is a simple HTTP route multiplexer that parses a request path,\n// records any URL params, and executes an end handler. It implements\n// the http.Handler interface and is friendly with the standard library.\n//\n// Mux is designed to be fast, minimal and offer a powerful API for building\n// modular and composable HTTP services with a large set of handlers. It's\n// particularly useful for writing large REST API services that break a handler\n// into many smaller parts composed of middlewares and end handlers.\ntype Mux struct {\n\t// The computed mux handler made of the chained middleware stack and\n\t// the tree router\n\thandler http.Handler\n\n\t// The radix trie router\n\ttree *node\n\n\t// Custom method not allowed handler\n\tmethodNotAllowedHandler http.HandlerFunc\n\n\t// A reference to the parent mux used by subrouters when mounting\n\t// to a parent mux\n\tparent *Mux\n\n\t// Routing context pool\n\tpool *sync.Pool\n\n\t// Custom route not found handler\n\tnotFoundHandler http.HandlerFunc\n\n\t// The middleware stack\n\tmiddlewares []func(http.Handler) http.Handler\n\n\t// Controls the behaviour of middleware chain generation when a mux\n\t// is registered as an inline group inside another mux.\n\tinline bool\n}\n\n// NewMux returns a newly initialized Mux object that implements the Router\n// interface.\nfunc NewMux() *Mux {\n\tmux := &Mux{tree: &node{}, pool: &sync.Pool{}}\n\tmux.pool.New = func() interface{} {\n\t\treturn NewRouteContext()\n\t}\n\treturn mux\n}\n\n// ServeHTTP is the single method of the http.Handler interface that makes\n// Mux interoperable with the standard library. It uses a sync.Pool to get and\n// reuse routing contexts for each request.\nfunc (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\t// Ensure the mux has some routes defined on the mux\n\tif mx.handler == nil {\n\t\tmx.NotFoundHandler().ServeHTTP(w, r)\n\t\treturn\n\t}\n\n\t// Check if a routing context already exists from a parent router.\n\trctx, _ := r.Context().Value(RouteCtxKey).(*Context)\n\tif rctx != nil {\n\t\tmx.handler.ServeHTTP(w, r)\n\t\treturn\n\t}\n\n\t// Fetch a RouteContext object from the sync pool, and call the computed\n\t// mx.handler that is comprised of mx.middlewares + mx.routeHTTP.\n\t// Once the request is finished, reset the routing context and put it back\n\t// into the pool for reuse from another request.\n\trctx = mx.pool.Get().(*Context)\n\trctx.Reset()\n\trctx.Routes = mx\n\trctx.parentCtx = r.Context()\n\n\t// NOTE: r.WithContext() causes 2 allocations and context.WithValue() causes 1 allocation\n\tr = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))\n\n\t// Serve the request and once its done, put the request context back in the sync pool\n\tmx.handler.ServeHTTP(w, r)\n\tmx.pool.Put(rctx)\n}\n\n// Use appends a middleware handler to the Mux middleware stack.\n//\n// The middleware stack for any Mux will execute before searching for a matching\n// route to a specific handler, which provides opportunity to respond early,\n// change the course of the request execution, or set request-scoped values for\n// the next http.Handler.\nfunc (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) {\n\tif mx.handler != nil {\n\t\tpanic(\"chi: all middlewares must be defined before routes on a mux\")\n\t}\n\tmx.middlewares = append(mx.middlewares, middlewares...)\n}\n\n// Handle adds the route `pattern` that matches any http method to\n// execute the `handler` http.Handler.\nfunc (mx *Mux) Handle(pattern string, handler http.Handler) {\n\tif i := strings.IndexAny(pattern, \" \\t\"); i >= 0 {\n\t\tmethod, rest := pattern[:i], strings.TrimLeft(pattern[i+1:], \" \\t\")\n\t\tmx.Method(method, rest, handler)\n\t\treturn\n\t}\n\n\tmx.handle(mALL, pattern, handler)\n}\n\n// HandleFunc adds the route `pattern` that matches any http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) {\n\tmx.Handle(pattern, handlerFn)\n}\n\n// Method adds the route `pattern` that matches `method` http method to\n// execute the `handler` http.Handler.\nfunc (mx *Mux) Method(method, pattern string, handler http.Handler) {\n\tm, ok := methodMap[strings.ToUpper(method)]\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"chi: '%s' http method is not supported.\", method))\n\t}\n\tmx.handle(m, pattern, handler)\n}\n\n// MethodFunc adds the route `pattern` that matches `method` http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) {\n\tmx.Method(method, pattern, handlerFn)\n}\n\n// Connect adds the route `pattern` that matches a CONNECT http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mCONNECT, pattern, handlerFn)\n}\n\n// Delete adds the route `pattern` that matches a DELETE http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mDELETE, pattern, handlerFn)\n}\n\n// Get adds the route `pattern` that matches a GET http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mGET, pattern, handlerFn)\n}\n\n// Head adds the route `pattern` that matches a HEAD http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mHEAD, pattern, handlerFn)\n}\n\n// Options adds the route `pattern` that matches an OPTIONS http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mOPTIONS, pattern, handlerFn)\n}\n\n// Patch adds the route `pattern` that matches a PATCH http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mPATCH, pattern, handlerFn)\n}\n\n// Post adds the route `pattern` that matches a POST http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mPOST, pattern, handlerFn)\n}\n\n// Put adds the route `pattern` that matches a PUT http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mPUT, pattern, handlerFn)\n}\n\n// Trace adds the route `pattern` that matches a TRACE http method to\n// execute the `handlerFn` http.HandlerFunc.\nfunc (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) {\n\tmx.handle(mTRACE, pattern, handlerFn)\n}\n\n// NotFound sets a custom http.HandlerFunc for routing paths that could\n// not be found. The default 404 handler is `http.NotFound`.\nfunc (mx *Mux) NotFound(handlerFn http.HandlerFunc) {\n\t// Build NotFound handler chain\n\tm := mx\n\thFn := handlerFn\n\tif mx.inline && mx.parent != nil {\n\t\tm = mx.parent\n\t\thFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP\n\t}\n\n\t// Update the notFoundHandler from this point forward\n\tm.notFoundHandler = hFn\n\tm.updateSubRoutes(func(subMux *Mux) {\n\t\tif subMux.notFoundHandler == nil {\n\t\t\tsubMux.NotFound(hFn)\n\t\t}\n\t})\n}\n\n// MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the\n// method is unresolved. The default handler returns a 405 with an empty body.\nfunc (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) {\n\t// Build MethodNotAllowed handler chain\n\tm := mx\n\thFn := handlerFn\n\tif mx.inline && mx.parent != nil {\n\t\tm = mx.parent\n\t\thFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP\n\t}\n\n\t// Update the methodNotAllowedHandler from this point forward\n\tm.methodNotAllowedHandler = hFn\n\tm.updateSubRoutes(func(subMux *Mux) {\n\t\tif subMux.methodNotAllowedHandler == nil {\n\t\t\tsubMux.MethodNotAllowed(hFn)\n\t\t}\n\t})\n}\n\n// With adds inline middlewares for an endpoint handler.\nfunc (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {\n\t// Similarly as in handle(), we must build the mux handler once additional\n\t// middleware registration isn't allowed for this stack, like now.\n\tif !mx.inline && mx.handler == nil {\n\t\tmx.updateRouteHandler()\n\t}\n\n\t// Copy middlewares from parent inline muxs\n\tvar mws Middlewares\n\tif mx.inline {\n\t\tmws = make(Middlewares, len(mx.middlewares))\n\t\tcopy(mws, mx.middlewares)\n\t}\n\tmws = append(mws, middlewares...)\n\n\tim := &Mux{\n\t\tpool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws,\n\t\tnotFoundHandler: mx.notFoundHandler, methodNotAllowedHandler: mx.methodNotAllowedHandler,\n\t}\n\n\treturn im\n}\n\n// Group creates a new inline-Mux with a copy of middleware stack. It's useful\n// for a group of handlers along the same routing path that use an additional\n// set of middlewares. See _examples/.\nfunc (mx *Mux) Group(fn func(r Router)) Router {\n\tim := mx.With()\n\tif fn != nil {\n\t\tfn(im)\n\t}\n\treturn im\n}\n\n// Route creates a new Mux and mounts it along the `pattern` as a subrouter.\n// Effectively, this is a short-hand call to Mount. See _examples/.\nfunc (mx *Mux) Route(pattern string, fn func(r Router)) Router {\n\tif fn == nil {\n\t\tpanic(fmt.Sprintf(\"chi: attempting to Route() a nil subrouter on '%s'\", pattern))\n\t}\n\tsubRouter := NewRouter()\n\tfn(subRouter)\n\tmx.Mount(pattern, subRouter)\n\treturn subRouter\n}\n\n// Mount attaches another http.Handler or chi Router as a subrouter along a routing\n// path. It's very useful to split up a large API as many independent routers and\n// compose them as a single service using Mount. See _examples/.\n//\n// Note that Mount() simply sets a wildcard along the `pattern` that will continue\n// routing at the `handler`, which in most cases is another chi.Router. As a result,\n// if you define two Mount() routes on the exact same pattern the mount will panic.\nfunc (mx *Mux) Mount(pattern string, handler http.Handler) {\n\tif handler == nil {\n\t\tpanic(fmt.Sprintf(\"chi: attempting to Mount() a nil handler on '%s'\", pattern))\n\t}\n\n\t// Provide runtime safety for ensuring a pattern isn't mounted on an existing\n\t// routing pattern.\n\tif mx.tree.findPattern(pattern+\"*\") || mx.tree.findPattern(pattern+\"/*\") {\n\t\tpanic(fmt.Sprintf(\"chi: attempting to Mount() a handler on an existing path, '%s'\", pattern))\n\t}\n\n\t// Assign sub-Router's with the parent not found & method not allowed handler if not specified.\n\tsubr, ok := handler.(*Mux)\n\tif ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil {\n\t\tsubr.NotFound(mx.notFoundHandler)\n\t}\n\tif ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil {\n\t\tsubr.MethodNotAllowed(mx.methodNotAllowedHandler)\n\t}\n\n\tmountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\trctx := RouteContext(r.Context())\n\n\t\t// shift the url path past the previous subrouter\n\t\trctx.RoutePath = mx.nextRoutePath(rctx)\n\n\t\t// reset the wildcard URLParam which connects the subrouter\n\t\tn := len(rctx.URLParams.Keys) - 1\n\t\tif n >= 0 && rctx.URLParams.Keys[n] == \"*\" && len(rctx.URLParams.Values) > n {\n\t\t\trctx.URLParams.Values[n] = \"\"\n\t\t}\n\n\t\thandler.ServeHTTP(w, r)\n\t})\n\n\tif pattern == \"\" || pattern[len(pattern)-1] != '/' {\n\t\tmx.handle(mALL|mSTUB, pattern, mountHandler)\n\t\tmx.handle(mALL|mSTUB, pattern+\"/\", mountHandler)\n\t\tpattern += \"/\"\n\t}\n\n\tmethod := mALL\n\tsubroutes, _ := handler.(Routes)\n\tif subroutes != nil {\n\t\tmethod |= mSTUB\n\t}\n\tn := mx.handle(method, pattern+\"*\", mountHandler)\n\n\tif subroutes != nil {\n\t\tn.subroutes = subroutes\n\t}\n}\n\n// Routes returns a slice of routing information from the tree,\n// useful for traversing available routes of a router.\nfunc (mx *Mux) Routes() []Route {\n\treturn mx.tree.routes()\n}\n\n// Middlewares returns a slice of middleware handler functions.\nfunc (mx *Mux) Middlewares() Middlewares {\n\treturn mx.middlewares\n}\n\n// Match searches the routing tree for a handler that matches the method/path.\n// It's similar to routing a http request, but without executing the handler\n// thereafter.\n//\n// Note: the *Context state is updated during execution, so manage\n// the state carefully or make a NewRouteContext().\nfunc (mx *Mux) Match(rctx *Context, method, path string) bool {\n\treturn mx.Find(rctx, method, path) != \"\"\n}\n\n// Find searches the routing tree for the pattern that matches\n// the method/path.\n//\n// Note: the *Context state is updated during execution, so manage\n// the state carefully or make a NewRouteContext().\nfunc (mx *Mux) Find(rctx *Context, method, path string) string {\n\tm, ok := methodMap[method]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\n\tnode, _, _ := mx.tree.FindRoute(rctx, m, path)\n\tpattern := rctx.routePattern\n\n\tif node != nil {\n\t\tif node.subroutes == nil {\n\t\t\te := node.endpoints[m]\n\t\t\treturn e.pattern\n\t\t}\n\n\t\trctx.RoutePath = mx.nextRoutePath(rctx)\n\t\tsubPattern := node.subroutes.Find(rctx, method, rctx.RoutePath)\n\t\tif subPattern == \"\" {\n\t\t\treturn \"\"\n\t\t}\n\n\t\tpattern = strings.TrimSuffix(pattern, \"/*\")\n\t\tpattern += subPattern\n\t}\n\n\treturn pattern\n}\n\n// NotFoundHandler returns the default Mux 404 responder whenever a route\n// cannot be found.\nfunc (mx *Mux) NotFoundHandler() http.HandlerFunc {\n\tif mx.notFoundHandler != nil {\n\t\treturn mx.notFoundHandler\n\t}\n\treturn http.NotFound\n}\n\n// MethodNotAllowedHandler returns the default Mux 405 responder whenever\n// a method cannot be resolved for a route.\nfunc (mx *Mux) MethodNotAllowedHandler(methodsAllowed ...methodTyp) http.HandlerFunc {\n\tif mx.methodNotAllowedHandler != nil {\n\t\treturn mx.methodNotAllowedHandler\n\t}\n\treturn methodNotAllowedHandler(methodsAllowed...)\n}\n\n// handle registers a http.Handler in the routing tree for a particular http method\n// and routing pattern.\nfunc (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node {\n\tif len(pattern) == 0 || pattern[0] != '/' {\n\t\tpanic(fmt.Sprintf(\"chi: routing pattern must begin with '/' in '%s'\", pattern))\n\t}\n\n\t// Build the computed routing handler for this routing pattern.\n\tif !mx.inline && mx.handler == nil {\n\t\tmx.updateRouteHandler()\n\t}\n\n\t// Build endpoint handler with inline middlewares for the route\n\tvar h http.Handler\n\tif mx.inline {\n\t\tmx.handler = http.HandlerFunc(mx.routeHTTP)\n\t\th = Chain(mx.middlewares...).Handler(handler)\n\t} else {\n\t\th = handler\n\t}\n\n\t// Add the endpoint to the tree and return the node\n\treturn mx.tree.InsertRoute(method, pattern, h)\n}\n\n// routeHTTP routes a http.Request through the Mux routing tree to serve\n// the matching handler for a particular http method.\nfunc (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {\n\t// Grab the route context object\n\trctx := r.Context().Value(RouteCtxKey).(*Context)\n\n\t// The request routing path\n\troutePath := rctx.RoutePath\n\tif routePath == \"\" {\n\t\tif r.URL.RawPath != \"\" {\n\t\t\troutePath = r.URL.RawPath\n\t\t} else {\n\t\t\troutePath = r.URL.Path\n\t\t}\n\t\tif routePath == \"\" {\n\t\t\troutePath = \"/\"\n\t\t}\n\t}\n\n\t// Check if method is supported by chi\n\tif rctx.RouteMethod == \"\" {\n\t\trctx.RouteMethod = r.Method\n\t}\n\tmethod, ok := methodMap[rctx.RouteMethod]\n\tif !ok {\n\t\tmx.MethodNotAllowedHandler().ServeHTTP(w, r)\n\t\treturn\n\t}\n\n\t// Find the route\n\tif _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {\n\t\t// Set http.Request path values from our request context\n\t\tfor i, key := range rctx.URLParams.Keys {\n\t\t\tvalue := rctx.URLParams.Values[i]\n\t\t\tr.SetPathValue(key, value)\n\t\t}\n\t\tr.Pattern = rctx.routePattern\n\n\t\th.ServeHTTP(w, r)\n\t\treturn\n\t}\n\tif rctx.methodNotAllowed {\n\t\tmx.MethodNotAllowedHandler(rctx.methodsAllowed...).ServeHTTP(w, r)\n\t} else {\n\t\tmx.NotFoundHandler().ServeHTTP(w, r)\n\t}\n}\n\nfunc (mx *Mux) nextRoutePath(rctx *Context) string {\n\troutePath := \"/\"\n\tnx := len(rctx.routeParams.Keys) - 1 // index of last param in list\n\tif nx >= 0 && rctx.routeParams.Keys[nx] == \"*\" && len(rctx.routeParams.Values) > nx {\n\t\troutePath = \"/\" + rctx.routeParams.Values[nx]\n\t}\n\treturn routePath\n}\n\n// Recursively update data on child routers.\nfunc (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) {\n\tfor _, r := range mx.tree.routes() {\n\t\tsubMux, ok := r.SubRoutes.(*Mux)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tfn(subMux)\n\t}\n}\n\n// updateRouteHandler builds the single mux handler that is a chain of the middleware\n// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this\n// point, no other middlewares can be registered on this Mux's stack. But you can still\n// compose additional middlewares via Group()'s or using a chained middleware handler.\nfunc (mx *Mux) updateRouteHandler() {\n\tmx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))\n}\n\n// methodNotAllowedHandler is a helper function to respond with a 405,\n// method not allowed. It sets the Allow header with the list of allowed\n// methods for the route.\nfunc methodNotAllowedHandler(methodsAllowed ...methodTyp) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tfor _, m := range methodsAllowed {\n\t\t\tw.Header().Add(\"Allow\", reverseMethodMap[m])\n\t\t}\n\t\tw.WriteHeader(405)\n\t\tw.Write(nil)\n\t}\n}\n"
  },
  {
    "path": "mux_test.go",
    "content": "package chi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestMuxBasic(t *testing.T) {\n\tvar count uint64\n\tcountermw := func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tcount++\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tusermw := func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\tctx = context.WithValue(ctx, ctxKey{\"user\"}, \"peter\")\n\t\t\tr = r.WithContext(ctx)\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\texmw := func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := context.WithValue(r.Context(), ctxKey{\"ex\"}, \"a\")\n\t\t\tr = r.WithContext(ctx)\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tlogbuf := bytes.NewBufferString(\"\")\n\tlogmsg := \"logmw test\"\n\tlogmw := func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tlogbuf.WriteString(logmsg)\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tcxindex := func(w http.ResponseWriter, r *http.Request) {\n\t\tctx := r.Context()\n\t\tuser := ctx.Value(ctxKey{\"user\"}).(string)\n\t\tw.WriteHeader(200)\n\t\tw.Write([]byte(fmt.Sprintf(\"hi %s\", user)))\n\t}\n\n\tping := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(200)\n\t\tw.Write([]byte(\".\"))\n\t}\n\n\theadPing := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Ping\", \"1\")\n\t\tw.WriteHeader(200)\n\t}\n\n\tcreatePing := func(w http.ResponseWriter, r *http.Request) {\n\t\t// create ....\n\t\tw.WriteHeader(201)\n\t}\n\n\tpingAll := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(200)\n\t\tw.Write([]byte(\"ping all\"))\n\t}\n\n\tpingAll2 := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(200)\n\t\tw.Write([]byte(\"ping all2\"))\n\t}\n\n\tpingOne := func(w http.ResponseWriter, r *http.Request) {\n\t\tidParam := URLParam(r, \"id\")\n\t\tw.WriteHeader(200)\n\t\tw.Write([]byte(fmt.Sprintf(\"ping one id: %s\", idParam)))\n\t}\n\n\tpingWoop := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(200)\n\t\tw.Write([]byte(\"woop.\" + URLParam(r, \"iidd\")))\n\t}\n\n\tcatchAll := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(200)\n\t\tw.Write([]byte(\"catchall\"))\n\t}\n\n\tm := NewRouter()\n\tm.Use(countermw)\n\tm.Use(usermw)\n\tm.Use(exmw)\n\tm.Use(logmw)\n\tm.Get(\"/\", cxindex)\n\tm.Method(\"GET\", \"/ping\", http.HandlerFunc(ping))\n\tm.MethodFunc(\"GET\", \"/pingall\", pingAll)\n\tm.MethodFunc(\"get\", \"/ping/all\", pingAll)\n\tm.Get(\"/ping/all2\", pingAll2)\n\n\tm.Head(\"/ping\", headPing)\n\tm.Post(\"/ping\", createPing)\n\tm.Get(\"/ping/{id}\", pingWoop)\n\tm.Get(\"/ping/{id}\", pingOne) // expected to overwrite to pingOne handler\n\tm.Get(\"/ping/{iidd}/woop\", pingWoop)\n\tm.HandleFunc(\"/admin/*\", catchAll)\n\t// m.Post(\"/admin/*\", catchAll)\n\n\tts := httptest.NewServer(m)\n\tdefer ts.Close()\n\n\t// GET /\n\tif _, body := testRequest(t, ts, \"GET\", \"/\", nil); body != \"hi peter\" {\n\t\tt.Fatal(body)\n\t}\n\ttlogmsg, _ := logbuf.ReadString(0)\n\tif tlogmsg != logmsg {\n\t\tt.Error(\"expecting log message from middleware:\", logmsg)\n\t}\n\n\t// GET /ping\n\tif _, body := testRequest(t, ts, \"GET\", \"/ping\", nil); body != \".\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// GET /pingall\n\tif _, body := testRequest(t, ts, \"GET\", \"/pingall\", nil); body != \"ping all\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// GET /ping/all\n\tif _, body := testRequest(t, ts, \"GET\", \"/ping/all\", nil); body != \"ping all\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// GET /ping/all2\n\tif _, body := testRequest(t, ts, \"GET\", \"/ping/all2\", nil); body != \"ping all2\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// GET /ping/123\n\tif _, body := testRequest(t, ts, \"GET\", \"/ping/123\", nil); body != \"ping one id: 123\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// GET /ping/allan\n\tif _, body := testRequest(t, ts, \"GET\", \"/ping/allan\", nil); body != \"ping one id: allan\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// GET /ping/1/woop\n\tif _, body := testRequest(t, ts, \"GET\", \"/ping/1/woop\", nil); body != \"woop.1\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// HEAD /ping\n\tresp, err := http.Head(ts.URL + \"/ping\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif resp.StatusCode != 200 {\n\t\tt.Error(\"head failed, should be 200\")\n\t}\n\tif resp.Header.Get(\"X-Ping\") == \"\" {\n\t\tt.Error(\"expecting X-Ping header\")\n\t}\n\n\t// GET /admin/catch-this\n\tif _, body := testRequest(t, ts, \"GET\", \"/admin/catch-thazzzzz\", nil); body != \"catchall\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// POST /admin/catch-this\n\tresp, err = http.Post(ts.URL+\"/admin/casdfsadfs\", \"text/plain\", bytes.NewReader([]byte{}))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tt.Error(\"POST failed, should be 200\")\n\t}\n\n\tif string(body) != \"catchall\" {\n\t\tt.Error(\"expecting response body: 'catchall'\")\n\t}\n\n\t// Custom http method DIE /ping/1/woop\n\tif resp, body := testRequest(t, ts, \"DIE\", \"/ping/1/woop\", nil); body != \"\" || resp.StatusCode != 405 {\n\t\tt.Fatalf(\"expecting 405 status and empty body, got %d '%s'\", resp.StatusCode, body)\n\t}\n}\n\nfunc TestMuxMounts(t *testing.T) {\n\tr := NewRouter()\n\n\tr.Get(\"/{hash}\", func(w http.ResponseWriter, r *http.Request) {\n\t\tv := URLParam(r, \"hash\")\n\t\tw.Write([]byte(fmt.Sprintf(\"/%s\", v)))\n\t})\n\n\tr.Route(\"/{hash}/share\", func(r Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tv := URLParam(r, \"hash\")\n\t\t\tw.Write([]byte(fmt.Sprintf(\"/%s/share\", v)))\n\t\t})\n\t\tr.Get(\"/{network}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tv := URLParam(r, \"hash\")\n\t\t\tn := URLParam(r, \"network\")\n\t\t\tw.Write([]byte(fmt.Sprintf(\"/%s/share/%s\", v, n)))\n\t\t})\n\t})\n\n\tm := NewRouter()\n\tm.Mount(\"/sharing\", r)\n\n\tts := httptest.NewServer(m)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/sharing/aBc\", nil); body != \"/aBc\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/sharing/aBc/share\", nil); body != \"/aBc/share\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/sharing/aBc/share/twitter\", nil); body != \"/aBc/share/twitter\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxPlain(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"bye\"))\n\t})\n\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(\"nothing here\"))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/hi\", nil); body != \"bye\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/nothing-here\", nil); body != \"nothing here\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxEmptyRoutes(t *testing.T) {\n\tmux := NewRouter()\n\n\tapiRouter := NewRouter()\n\t// oops, we forgot to declare any route handlers\n\n\tmux.Handle(\"/api*\", apiRouter)\n\n\tif _, body := testHandler(t, mux, \"GET\", \"/\", nil); body != \"404 page not found\\n\" {\n\t\tt.Fatal(body)\n\t}\n\n\tif _, body := testHandler(t, apiRouter, \"GET\", \"/\", nil); body != \"404 page not found\\n\" {\n\t\tt.Fatal(body)\n\t}\n}\n\n// Test a mux that routes a trailing slash, see also middleware/strip_test.go\n// for an example of using a middleware to handle trailing slashes.\nfunc TestMuxTrailingSlash(t *testing.T) {\n\tr := NewRouter()\n\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(\"nothing here\"))\n\t})\n\n\tsubRoutes := NewRouter()\n\tindexHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\taccountID := URLParam(r, \"accountID\")\n\t\tw.Write([]byte(accountID))\n\t}\n\tsubRoutes.Get(\"/\", indexHandler)\n\n\tr.Mount(\"/accounts/{accountID}\", subRoutes)\n\tr.Get(\"/accounts/{accountID}/\", indexHandler)\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/accounts/admin\", nil); body != \"admin\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/accounts/admin/\", nil); body != \"admin\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/nothing-here\", nil); body != \"nothing here\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxNestedNotFound(t *testing.T) {\n\tr := NewRouter()\n\n\tr.Use(func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tr = r.WithContext(context.WithValue(r.Context(), ctxKey{\"mw\"}, \"mw\"))\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"bye\"))\n\t})\n\n\tr.With(func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tr = r.WithContext(context.WithValue(r.Context(), ctxKey{\"with\"}, \"with\"))\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}).NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\tchkMw := r.Context().Value(ctxKey{\"mw\"}).(string)\n\t\tchkWith := r.Context().Value(ctxKey{\"with\"}).(string)\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(fmt.Sprintf(\"root 404 %s %s\", chkMw, chkWith)))\n\t})\n\n\tsr1 := NewRouter()\n\n\tsr1.Get(\"/sub\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"sub\"))\n\t})\n\tsr1.Group(func(sr1 Router) {\n\t\tsr1.Use(func(next http.Handler) http.Handler {\n\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tr = r.WithContext(context.WithValue(r.Context(), ctxKey{\"mw2\"}, \"mw2\"))\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t})\n\t\t})\n\t\tsr1.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tchkMw2 := r.Context().Value(ctxKey{\"mw2\"}).(string)\n\t\t\tw.WriteHeader(404)\n\t\t\tw.Write([]byte(fmt.Sprintf(\"sub 404 %s\", chkMw2)))\n\t\t})\n\t})\n\n\tsr2 := NewRouter()\n\tsr2.Get(\"/sub\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"sub2\"))\n\t})\n\n\tr.Mount(\"/admin1\", sr1)\n\tr.Mount(\"/admin2\", sr2)\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/hi\", nil); body != \"bye\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/nothing-here\", nil); body != \"root 404 mw with\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/admin1/sub\", nil); body != \"sub\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/admin1/nope\", nil); body != \"sub 404 mw2\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/admin2/sub\", nil); body != \"sub2\" {\n\t\tt.Fatal(body)\n\t}\n\n\t// Not found pages should bubble up to the root.\n\tif _, body := testRequest(t, ts, \"GET\", \"/admin2/nope\", nil); body != \"root 404 mw with\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMethodNotAllowed(t *testing.T) {\n\tr := NewRouter()\n\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hi, get\"))\n\t})\n\n\tr.Head(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hi, head\"))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tt.Run(\"Registered Method\", func(t *testing.T) {\n\t\tresp, _ := testRequest(t, ts, \"GET\", \"/hi\", nil)\n\t\tif resp.StatusCode != 200 {\n\t\t\tt.Fatal(resp.Status)\n\t\t}\n\t\tif resp.Header.Values(\"Allow\") != nil {\n\t\t\tt.Fatal(\"allow should be empty when method is registered\")\n\t\t}\n\t})\n\n\tt.Run(\"Unregistered Method\", func(t *testing.T) {\n\t\tresp, _ := testRequest(t, ts, \"POST\", \"/hi\", nil)\n\t\tif resp.StatusCode != 405 {\n\t\t\tt.Fatal(resp.Status)\n\t\t}\n\t\tallowedMethods := resp.Header.Values(\"Allow\")\n\t\tif len(allowedMethods) != 2 || ((allowedMethods[0] != \"GET\" || allowedMethods[1] != \"HEAD\") &&\n\t\t\t(allowedMethods[1] != \"GET\" || allowedMethods[0] != \"HEAD\")) {\n\t\t\tt.Fatal(\"Allow header should contain 2 headers: GET, HEAD. Received: \", allowedMethods)\n\t\t}\n\t})\n}\n\nfunc TestMuxNestedMethodNotAllowed(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/root\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"root\"))\n\t})\n\tr.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(405)\n\t\tw.Write([]byte(\"root 405\"))\n\t})\n\n\tsr1 := NewRouter()\n\tsr1.Get(\"/sub1\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"sub1\"))\n\t})\n\tsr1.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(405)\n\t\tw.Write([]byte(\"sub1 405\"))\n\t})\n\n\tsr2 := NewRouter()\n\tsr2.Get(\"/sub2\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"sub2\"))\n\t})\n\n\tpathVar := NewRouter()\n\tpathVar.Get(\"/{var}\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"pv\"))\n\t})\n\tpathVar.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(405)\n\t\tw.Write([]byte(\"pv 405\"))\n\t})\n\n\tr.Mount(\"/prefix1\", sr1)\n\tr.Mount(\"/prefix2\", sr2)\n\tr.Mount(\"/pathVar\", pathVar)\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/root\", nil); body != \"root\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"PUT\", \"/root\", nil); body != \"root 405\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/prefix1/sub1\", nil); body != \"sub1\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"PUT\", \"/prefix1/sub1\", nil); body != \"sub1 405\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/prefix2/sub2\", nil); body != \"sub2\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"PUT\", \"/prefix2/sub2\", nil); body != \"root 405\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/pathVar/myvar\", nil); body != \"pv\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"DELETE\", \"/pathVar/myvar\", nil); body != \"pv 405\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxComplicatedNotFound(t *testing.T) {\n\tdecorateRouter := func(r *Mux) {\n\t\t// Root router with groups\n\t\tr.Get(\"/auth\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"auth get\"))\n\t\t})\n\t\tr.Route(\"/public\", func(r Router) {\n\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.Write([]byte(\"public get\"))\n\t\t\t})\n\t\t})\n\n\t\t// sub router with groups\n\t\tsub0 := NewRouter()\n\t\tsub0.Route(\"/resource\", func(r Router) {\n\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.Write([]byte(\"private get\"))\n\t\t\t})\n\t\t})\n\t\tr.Mount(\"/private\", sub0)\n\n\t\t// sub router with groups\n\t\tsub1 := NewRouter()\n\t\tsub1.Route(\"/resource\", func(r Router) {\n\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.Write([]byte(\"private get\"))\n\t\t\t})\n\t\t})\n\t\tr.With(func(next http.Handler) http.Handler { return next }).Mount(\"/private_mw\", sub1)\n\t}\n\n\ttestNotFound := func(t *testing.T, r *Mux) {\n\t\tts := httptest.NewServer(r)\n\t\tdefer ts.Close()\n\n\t\t// check that we didn't break correct routes\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/auth\", nil); body != \"auth get\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/public\", nil); body != \"public get\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/public/\", nil); body != \"public get\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/private/resource\", nil); body != \"private get\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\t// check custom not-found on all levels\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/nope\", nil); body != \"custom not-found\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/public/nope\", nil); body != \"custom not-found\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/private/nope\", nil); body != \"custom not-found\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/private/resource/nope\", nil); body != \"custom not-found\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/private_mw/nope\", nil); body != \"custom not-found\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/private_mw/resource/nope\", nil); body != \"custom not-found\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t\t// check custom not-found on trailing slash routes\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/auth/\", nil); body != \"custom not-found\" {\n\t\t\tt.Fatal(body)\n\t\t}\n\t}\n\n\tt.Run(\"pre\", func(t *testing.T) {\n\t\tr := NewRouter()\n\t\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"custom not-found\"))\n\t\t})\n\t\tdecorateRouter(r)\n\t\ttestNotFound(t, r)\n\t})\n\n\tt.Run(\"post\", func(t *testing.T) {\n\t\tr := NewRouter()\n\t\tdecorateRouter(r)\n\t\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"custom not-found\"))\n\t\t})\n\t\ttestNotFound(t, r)\n\t})\n}\n\nfunc TestMuxWith(t *testing.T) {\n\tvar cmwInit1, cmwHandler1 uint64\n\tvar cmwInit2, cmwHandler2 uint64\n\tmw1 := func(next http.Handler) http.Handler {\n\t\tcmwInit1++\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tcmwHandler1++\n\t\t\tr = r.WithContext(context.WithValue(r.Context(), ctxKey{\"inline1\"}, \"yes\"))\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\tmw2 := func(next http.Handler) http.Handler {\n\t\tcmwInit2++\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tcmwHandler2++\n\t\t\tr = r.WithContext(context.WithValue(r.Context(), ctxKey{\"inline2\"}, \"yes\"))\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tr := NewRouter()\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"bye\"))\n\t})\n\tr.With(mw1).With(mw2).Get(\"/inline\", func(w http.ResponseWriter, r *http.Request) {\n\t\tv1 := r.Context().Value(ctxKey{\"inline1\"}).(string)\n\t\tv2 := r.Context().Value(ctxKey{\"inline2\"}).(string)\n\t\tw.Write([]byte(fmt.Sprintf(\"inline %s %s\", v1, v2)))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/hi\", nil); body != \"bye\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/inline\", nil); body != \"inline yes yes\" {\n\t\tt.Fatal(body)\n\t}\n\tif cmwInit1 != 1 {\n\t\tt.Fatalf(\"expecting cmwInit1 to be 1, got %d\", cmwInit1)\n\t}\n\tif cmwHandler1 != 1 {\n\t\tt.Fatalf(\"expecting cmwHandler1 to be 1, got %d\", cmwHandler1)\n\t}\n\tif cmwInit2 != 1 {\n\t\tt.Fatalf(\"expecting cmwInit2 to be 1, got %d\", cmwInit2)\n\t}\n\tif cmwHandler2 != 1 {\n\t\tt.Fatalf(\"expecting cmwHandler2 to be 1, got %d\", cmwHandler2)\n\t}\n}\n\nfunc TestMuxHandlePatternValidation(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tpattern        string\n\t\tmethod         string\n\t\tpath           string\n\t\texpectedBody   string\n\t\texpectedStatus int\n\t\tshouldPanic    bool\n\t}{\n\t\t// Valid patterns\n\t\t{\n\t\t\tname:           \"Valid pattern without HTTP GET\",\n\t\t\tpattern:        \"/user/{id}\",\n\t\t\tshouldPanic:    false,\n\t\t\tmethod:         \"GET\",\n\t\t\tpath:           \"/user/123\",\n\t\t\texpectedBody:   \"without-prefix GET\",\n\t\t\texpectedStatus: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:           \"Valid pattern with HTTP POST\",\n\t\t\tpattern:        \"POST /products/{id}\",\n\t\t\tshouldPanic:    false,\n\t\t\tmethod:         \"POST\",\n\t\t\tpath:           \"/products/456\",\n\t\t\texpectedBody:   \"with-prefix POST\",\n\t\t\texpectedStatus: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:           \"Valid pattern with multiple whitespace after method\",\n\t\t\tpattern:        \"PATCH \\t /\",\n\t\t\tshouldPanic:    false,\n\t\t\tmethod:         \"PATCH\",\n\t\t\tpath:           \"/\",\n\t\t\texpectedBody:   \"extended-whitespace PATCH\",\n\t\t\texpectedStatus: http.StatusOK,\n\t\t},\n\t\t// Invalid patterns\n\t\t{\n\t\t\tname:        \"Invalid pattern with no method\",\n\t\t\tpattern:     \"INVALID/user/{id}\",\n\t\t\tshouldPanic: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"Invalid pattern with supported method\",\n\t\t\tpattern:     \"GET/user/{id}\",\n\t\t\tshouldPanic: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"Invalid pattern with unsupported method\",\n\t\t\tpattern:     \"UNSUPPORTED /unsupported-method\",\n\t\t\tshouldPanic: 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\tdefer func() {\n\t\t\t\tif r := recover(); r != nil && !tc.shouldPanic {\n\t\t\t\t\tt.Errorf(\"Unexpected panic for pattern %s:\\n%v\", tc.pattern, r)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tr1 := NewRouter()\n\t\t\tr1.Handle(tc.pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.Write([]byte(tc.expectedBody))\n\t\t\t}))\n\n\t\t\t// Test that HandleFunc also handles method patterns\n\t\t\tr2 := NewRouter()\n\t\t\tr2.HandleFunc(tc.pattern, func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.Write([]byte(tc.expectedBody))\n\t\t\t})\n\n\t\t\tif !tc.shouldPanic {\n\t\t\t\tfor _, r := range []Router{r1, r2} {\n\t\t\t\t\t// Use testRequest for valid patterns\n\t\t\t\t\tts := httptest.NewServer(r)\n\t\t\t\t\tdefer ts.Close()\n\n\t\t\t\t\tresp, body := testRequest(t, ts, tc.method, tc.path, nil)\n\t\t\t\t\tif body != tc.expectedBody || resp.StatusCode != tc.expectedStatus {\n\t\t\t\t\t\tt.Errorf(\"Expected status %d and body %s; got status %d and body %s for pattern %s\",\n\t\t\t\t\t\t\ttc.expectedStatus, tc.expectedBody, resp.StatusCode, body, tc.pattern)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRouterFromMuxWith(t *testing.T) {\n\tt.Parallel()\n\n\tr := NewRouter()\n\n\twith := r.With(func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\twith.Get(\"/with_middleware\", func(w http.ResponseWriter, r *http.Request) {})\n\n\tts := httptest.NewServer(with)\n\tdefer ts.Close()\n\n\t// Without the fix this test was committed with, this causes a panic.\n\ttestRequest(t, ts, http.MethodGet, \"/with_middleware\", nil)\n}\n\nfunc TestMuxMiddlewareStack(t *testing.T) {\n\tvar stdmwInit, stdmwHandler uint64\n\tstdmw := func(next http.Handler) http.Handler {\n\t\tstdmwInit++\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tstdmwHandler++\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\t_ = stdmw\n\n\tvar ctxmwInit, ctxmwHandler uint64\n\tctxmw := func(next http.Handler) http.Handler {\n\t\tctxmwInit++\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctxmwHandler++\n\t\t\tctx := r.Context()\n\t\t\tctx = context.WithValue(ctx, ctxKey{\"count.ctxmwHandler\"}, ctxmwHandler)\n\t\t\tr = r.WithContext(ctx)\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tvar inCtxmwInit, inCtxmwHandler uint64\n\tinCtxmw := func(next http.Handler) http.Handler {\n\t\tinCtxmwInit++\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tinCtxmwHandler++\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tr := NewRouter()\n\tr.Use(stdmw)\n\tr.Use(ctxmw)\n\tr.Use(func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.URL.Path == \"/ping\" {\n\t\t\t\tw.Write([]byte(\"pong\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t})\n\n\tvar handlerCount uint64\n\n\tr.With(inCtxmw).Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\thandlerCount++\n\t\tctx := r.Context()\n\t\tctxmwHandlerCount := ctx.Value(ctxKey{\"count.ctxmwHandler\"}).(uint64)\n\t\tw.Write([]byte(fmt.Sprintf(\"inits:%d reqs:%d ctxValue:%d\", ctxmwInit, handlerCount, ctxmwHandlerCount)))\n\t})\n\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"wooot\"))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\ttestRequest(t, ts, \"GET\", \"/\", nil)\n\ttestRequest(t, ts, \"GET\", \"/\", nil)\n\tvar body string\n\t_, body = testRequest(t, ts, \"GET\", \"/\", nil)\n\tif body != \"inits:1 reqs:3 ctxValue:3\" {\n\t\tt.Fatalf(\"got: '%s'\", body)\n\t}\n\n\t_, body = testRequest(t, ts, \"GET\", \"/ping\", nil)\n\tif body != \"pong\" {\n\t\tt.Fatalf(\"got: '%s'\", body)\n\t}\n}\n\nfunc TestMuxRouteGroups(t *testing.T) {\n\tvar stdmwInit, stdmwHandler uint64\n\n\tstdmw := func(next http.Handler) http.Handler {\n\t\tstdmwInit++\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tstdmwHandler++\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tvar stdmwInit2, stdmwHandler2 uint64\n\tstdmw2 := func(next http.Handler) http.Handler {\n\t\tstdmwInit2++\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tstdmwHandler2++\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tr := NewRouter()\n\tr.Group(func(r Router) {\n\t\tr.Use(stdmw)\n\t\tr.Get(\"/group\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"root group\"))\n\t\t})\n\t})\n\tr.Group(func(r Router) {\n\t\tr.Use(stdmw2)\n\t\tr.Get(\"/group2\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"root group2\"))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\t// GET /group\n\t_, body := testRequest(t, ts, \"GET\", \"/group\", nil)\n\tif body != \"root group\" {\n\t\tt.Fatalf(\"got: '%s'\", body)\n\t}\n\tif stdmwInit != 1 || stdmwHandler != 1 {\n\t\tt.Logf(\"stdmw counters failed, should be 1:1, got %d:%d\", stdmwInit, stdmwHandler)\n\t}\n\n\t// GET /group2\n\t_, body = testRequest(t, ts, \"GET\", \"/group2\", nil)\n\tif body != \"root group2\" {\n\t\tt.Fatalf(\"got: '%s'\", body)\n\t}\n\tif stdmwInit2 != 1 || stdmwHandler2 != 1 {\n\t\tt.Fatalf(\"stdmw2 counters failed, should be 1:1, got %d:%d\", stdmwInit2, stdmwHandler2)\n\t}\n}\n\nfunc TestMuxBig(t *testing.T) {\n\tr := bigMux()\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tvar body, expected string\n\n\t_, body = testRequest(t, ts, \"GET\", \"/favicon.ico\", nil)\n\tif body != \"fav\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/4/view\", nil)\n\tif body != \"/hubs/4/view reqid:1 session:anonymous\" {\n\t\tt.Fatalf(\"got '%v'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/4/view/index.html\", nil)\n\tif body != \"/hubs/4/view/index.html reqid:1 session:anonymous\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"POST\", \"/hubs/ethereumhub/view/index.html\", nil)\n\tif body != \"/hubs/ethereumhub/view/index.html reqid:1 session:anonymous\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/\", nil)\n\tif body != \"/ reqid:1 session:elvis\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/suggestions\", nil)\n\tif body != \"/suggestions reqid:1 session:elvis\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/woot/444/hiiii\", nil)\n\tif body != \"/woot/444/hiiii\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/123\", nil)\n\texpected = \"/hubs/123 reqid:1 session:elvis\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/123/touch\", nil)\n\tif body != \"/hubs/123/touch reqid:1 session:elvis\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/123/webhooks\", nil)\n\tif body != \"/hubs/123/webhooks reqid:1 session:elvis\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/123/posts\", nil)\n\tif body != \"/hubs/123/posts reqid:1 session:elvis\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/folders\", nil)\n\tif body != \"404 page not found\\n\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/folders/\", nil)\n\tif body != \"/folders/ reqid:1 session:elvis\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/folders/public\", nil)\n\tif body != \"/folders/public reqid:1 session:elvis\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/folders/nothing\", nil)\n\tif body != \"404 page not found\\n\" {\n\t\tt.Fatalf(\"got '%s'\", body)\n\t}\n}\n\nfunc bigMux() Router {\n\tvar r *Mux\n\tvar sr3 *Mux\n\t// var sr1, sr2, sr3, sr4, sr5, sr6 *Mux\n\tr = NewRouter()\n\tr.Use(func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := context.WithValue(r.Context(), ctxKey{\"requestID\"}, \"1\")\n\t\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t\t})\n\t})\n\tr.Use(func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t})\n\tr.Group(func(r Router) {\n\t\tr.Use(func(next http.Handler) http.Handler {\n\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tctx := context.WithValue(r.Context(), ctxKey{\"session.user\"}, \"anonymous\")\n\t\t\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t\t\t})\n\t\t})\n\t\tr.Get(\"/favicon.ico\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"fav\"))\n\t\t})\n\t\tr.Get(\"/hubs/{hubID}/view\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\ts := fmt.Sprintf(\"/hubs/%s/view reqid:%s session:%s\", URLParam(r, \"hubID\"),\n\t\t\t\tctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\tw.Write([]byte(s))\n\t\t})\n\t\tr.Get(\"/hubs/{hubID}/view/*\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\ts := fmt.Sprintf(\"/hubs/%s/view/%s reqid:%s session:%s\", URLParamFromCtx(ctx, \"hubID\"),\n\t\t\t\tURLParam(r, \"*\"), ctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\tw.Write([]byte(s))\n\t\t})\n\t\tr.Post(\"/hubs/{hubSlug}/view/*\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\ts := fmt.Sprintf(\"/hubs/%s/view/%s reqid:%s session:%s\", URLParamFromCtx(ctx, \"hubSlug\"),\n\t\t\t\tURLParam(r, \"*\"), ctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\tw.Write([]byte(s))\n\t\t})\n\t})\n\tr.Group(func(r Router) {\n\t\tr.Use(func(next http.Handler) http.Handler {\n\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tctx := context.WithValue(r.Context(), ctxKey{\"session.user\"}, \"elvis\")\n\t\t\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t\t\t})\n\t\t})\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\ts := fmt.Sprintf(\"/ reqid:%s session:%s\", ctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\tw.Write([]byte(s))\n\t\t})\n\t\tr.Get(\"/suggestions\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\ts := fmt.Sprintf(\"/suggestions reqid:%s session:%s\", ctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\tw.Write([]byte(s))\n\t\t})\n\n\t\tr.Get(\"/woot/{wootID}/*\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\ts := fmt.Sprintf(\"/woot/%s/%s\", URLParam(r, \"wootID\"), URLParam(r, \"*\"))\n\t\t\tw.Write([]byte(s))\n\t\t})\n\n\t\tr.Route(\"/hubs\", func(r Router) {\n\t\t\t_ = r.(*Mux) // sr1\n\t\t\tr.Route(\"/{hubID}\", func(r Router) {\n\t\t\t\t_ = r.(*Mux) // sr2\n\t\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tctx := r.Context()\n\t\t\t\t\ts := fmt.Sprintf(\"/hubs/%s reqid:%s session:%s\",\n\t\t\t\t\t\tURLParam(r, \"hubID\"), ctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\t\t\tw.Write([]byte(s))\n\t\t\t\t})\n\t\t\t\tr.Get(\"/touch\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tctx := r.Context()\n\t\t\t\t\ts := fmt.Sprintf(\"/hubs/%s/touch reqid:%s session:%s\", URLParam(r, \"hubID\"),\n\t\t\t\t\t\tctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\t\t\tw.Write([]byte(s))\n\t\t\t\t})\n\n\t\t\t\tsr3 = NewRouter()\n\t\t\t\tsr3.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tctx := r.Context()\n\t\t\t\t\ts := fmt.Sprintf(\"/hubs/%s/webhooks reqid:%s session:%s\", URLParam(r, \"hubID\"),\n\t\t\t\t\t\tctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\t\t\tw.Write([]byte(s))\n\t\t\t\t})\n\t\t\t\tsr3.Route(\"/{webhookID}\", func(r Router) {\n\t\t\t\t\t_ = r.(*Mux) // sr4\n\t\t\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\t\tctx := r.Context()\n\t\t\t\t\t\ts := fmt.Sprintf(\"/hubs/%s/webhooks/%s reqid:%s session:%s\", URLParam(r, \"hubID\"),\n\t\t\t\t\t\t\tURLParam(r, \"webhookID\"), ctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\t\t\t\tw.Write([]byte(s))\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tr.Mount(\"/webhooks\", Chain(func(next http.Handler) http.Handler {\n\t\t\t\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\t\tnext.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKey{\"hook\"}, true)))\n\t\t\t\t\t})\n\t\t\t\t}).Handler(sr3))\n\n\t\t\t\tr.Route(\"/posts\", func(r Router) {\n\t\t\t\t\t_ = r.(*Mux) // sr5\n\t\t\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\t\tctx := r.Context()\n\t\t\t\t\t\ts := fmt.Sprintf(\"/hubs/%s/posts reqid:%s session:%s\", URLParam(r, \"hubID\"),\n\t\t\t\t\t\t\tctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\t\t\t\tw.Write([]byte(s))\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tr.Route(\"/folders/\", func(r Router) {\n\t\t\t_ = r.(*Mux) // sr6\n\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tctx := r.Context()\n\t\t\t\ts := fmt.Sprintf(\"/folders/ reqid:%s session:%s\",\n\t\t\t\t\tctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\t\tw.Write([]byte(s))\n\t\t\t})\n\t\t\tr.Get(\"/public\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tctx := r.Context()\n\t\t\t\ts := fmt.Sprintf(\"/folders/public reqid:%s session:%s\",\n\t\t\t\t\tctx.Value(ctxKey{\"requestID\"}), ctx.Value(ctxKey{\"session.user\"}))\n\t\t\t\tw.Write([]byte(s))\n\t\t\t})\n\t\t})\n\t})\n\n\treturn r\n}\n\nfunc TestMuxSubroutesBasic(t *testing.T) {\n\thIndex := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"index\"))\n\t})\n\thArticlesList := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"articles-list\"))\n\t})\n\thSearchArticles := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"search-articles\"))\n\t})\n\thGetArticle := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(fmt.Sprintf(\"get-article:%s\", URLParam(r, \"id\"))))\n\t})\n\thSyncArticle := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(fmt.Sprintf(\"sync-article:%s\", URLParam(r, \"id\"))))\n\t})\n\n\tr := NewRouter()\n\t// var rr1, rr2 *Mux\n\tr.Get(\"/\", hIndex)\n\tr.Route(\"/articles\", func(r Router) {\n\t\t// rr1 = r.(*Mux)\n\t\tr.Get(\"/\", hArticlesList)\n\t\tr.Get(\"/search\", hSearchArticles)\n\t\tr.Route(\"/{id}\", func(r Router) {\n\t\t\t// rr2 = r.(*Mux)\n\t\t\tr.Get(\"/\", hGetArticle)\n\t\t\tr.Get(\"/sync\", hSyncArticle)\n\t\t})\n\t})\n\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\t// debugPrintTree(0, 0, r.tree, 0)\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\t// debugPrintTree(0, 0, rr1.tree, 0)\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\t// debugPrintTree(0, 0, rr2.tree, 0)\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tvar body, expected string\n\n\t_, body = testRequest(t, ts, \"GET\", \"/\", nil)\n\texpected = \"index\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/articles\", nil)\n\texpected = \"articles-list\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/articles/search\", nil)\n\texpected = \"search-articles\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/articles/123\", nil)\n\texpected = \"get-article:123\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/articles/123/sync\", nil)\n\texpected = \"sync-article:123\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n}\n\nfunc TestMuxSubroutes(t *testing.T) {\n\thHubView1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hub1\"))\n\t})\n\thHubView2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hub2\"))\n\t})\n\thHubView3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hub3\"))\n\t})\n\thAccountView1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"account1\"))\n\t})\n\thAccountView2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"account2\"))\n\t})\n\n\tr := NewRouter()\n\tr.Get(\"/hubs/{hubID}/view\", hHubView1)\n\tr.Get(\"/hubs/{hubID}/view/*\", hHubView2)\n\n\tsr := NewRouter()\n\tsr.Get(\"/\", hHubView3)\n\tr.Mount(\"/hubs/{hubID}/users\", sr)\n\tr.Get(\"/hubs/{hubID}/users/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hub3 override\"))\n\t})\n\n\tsr3 := NewRouter()\n\tsr3.Get(\"/\", hAccountView1)\n\tsr3.Get(\"/hi\", hAccountView2)\n\n\t// var sr2 *Mux\n\tr.Route(\"/accounts/{accountID}\", func(r Router) {\n\t\t_ = r.(*Mux) // sr2\n\t\t// r.Get(\"/\", hAccountView1)\n\t\tr.Mount(\"/\", sr3)\n\t})\n\n\t// This is the same as the r.Route() call mounted on sr2\n\t// sr2 := NewRouter()\n\t// sr2.Mount(\"/\", sr3)\n\t// r.Mount(\"/accounts/{accountID}\", sr2)\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tvar body, expected string\n\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/123/view\", nil)\n\texpected = \"hub1\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/123/view/index.html\", nil)\n\texpected = \"hub2\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/123/users\", nil)\n\texpected = \"hub3\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/hubs/123/users/\", nil)\n\texpected = \"hub3 override\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/accounts/44\", nil)\n\texpected = \"account1\"\n\tif body != expected {\n\t\tt.Fatalf(\"request:%s expected:%s got:%s\", \"GET /accounts/44\", expected, body)\n\t}\n\t_, body = testRequest(t, ts, \"GET\", \"/accounts/44/hi\", nil)\n\texpected = \"account2\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\n\t// Test that we're building the routingPatterns properly\n\trouter := r\n\treq, _ := http.NewRequest(\"GET\", \"/accounts/44/hi\", nil)\n\n\trctx := NewRouteContext()\n\treq = req.WithContext(context.WithValue(req.Context(), RouteCtxKey, rctx))\n\n\tw := httptest.NewRecorder()\n\trouter.ServeHTTP(w, req)\n\n\tbody = w.Body.String()\n\texpected = \"account2\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n\n\troutePatterns := rctx.RoutePatterns\n\tif len(rctx.RoutePatterns) != 3 {\n\t\tt.Fatalf(\"expected 3 routing patterns, got:%d\", len(rctx.RoutePatterns))\n\t}\n\texpected = \"/accounts/{accountID}/*\"\n\tif routePatterns[0] != expected {\n\t\tt.Fatalf(\"routePattern, expected:%s got:%s\", expected, routePatterns[0])\n\t}\n\texpected = \"/*\"\n\tif routePatterns[1] != expected {\n\t\tt.Fatalf(\"routePattern, expected:%s got:%s\", expected, routePatterns[1])\n\t}\n\texpected = \"/hi\"\n\tif routePatterns[2] != expected {\n\t\tt.Fatalf(\"routePattern, expected:%s got:%s\", expected, routePatterns[2])\n\t}\n\n}\n\nfunc TestSingleHandler(t *testing.T) {\n\th := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tname := URLParam(r, \"name\")\n\t\tw.Write([]byte(\"hi \" + name))\n\t})\n\n\tr, _ := http.NewRequest(\"GET\", \"/\", nil)\n\trctx := NewRouteContext()\n\tr = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))\n\trctx.URLParams.Add(\"name\", \"joe\")\n\n\tw := httptest.NewRecorder()\n\th.ServeHTTP(w, r)\n\n\tbody := w.Body.String()\n\texpected := \"hi joe\"\n\tif body != expected {\n\t\tt.Fatalf(\"expected:%s got:%s\", expected, body)\n\t}\n}\n\n// TODO: a Router wrapper test..\n//\n// type ACLMux struct {\n// \t*Mux\n// \tXX string\n// }\n//\n// func NewACLMux() *ACLMux {\n// \treturn &ACLMux{Mux: NewRouter(), XX: \"hihi\"}\n// }\n//\n// // TODO: this should be supported...\n// func TestWoot(t *testing.T) {\n// \tvar r Router = NewRouter()\n//\n// \tvar r2 Router = NewACLMux() //NewRouter()\n// \tr2.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n// \t\tw.Write([]byte(\"hi\"))\n// \t})\n//\n// \tr.Mount(\"/\", r2)\n// }\n\nfunc TestServeHTTPExistingContext(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\ts, _ := r.Context().Value(ctxKey{\"testCtx\"}).(string)\n\t\tw.Write([]byte(s))\n\t})\n\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\ts, _ := r.Context().Value(ctxKey{\"testCtx\"}).(string)\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(s))\n\t})\n\n\ttestcases := []struct {\n\t\tCtx            context.Context\n\t\tMethod         string\n\t\tPath           string\n\t\tExpectedBody   string\n\t\tExpectedStatus int\n\t}{\n\t\t{\n\t\t\tMethod:         \"GET\",\n\t\t\tPath:           \"/hi\",\n\t\t\tCtx:            context.WithValue(context.Background(), ctxKey{\"testCtx\"}, \"hi ctx\"),\n\t\t\tExpectedStatus: 200,\n\t\t\tExpectedBody:   \"hi ctx\",\n\t\t},\n\t\t{\n\t\t\tMethod:         \"GET\",\n\t\t\tPath:           \"/hello\",\n\t\t\tCtx:            context.WithValue(context.Background(), ctxKey{\"testCtx\"}, \"nothing here ctx\"),\n\t\t\tExpectedStatus: 404,\n\t\t\tExpectedBody:   \"nothing here ctx\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tresp := httptest.NewRecorder()\n\t\treq, err := http.NewRequest(tc.Method, tc.Path, nil)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%v\", err)\n\t\t}\n\t\treq = req.WithContext(tc.Ctx)\n\t\tr.ServeHTTP(resp, req)\n\t\tb, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%v\", err)\n\t\t}\n\t\tif resp.Code != tc.ExpectedStatus {\n\t\t\tt.Fatalf(\"%v != %v\", tc.ExpectedStatus, resp.Code)\n\t\t}\n\t\tif string(b) != tc.ExpectedBody {\n\t\t\tt.Fatalf(\"%s != %s\", tc.ExpectedBody, b)\n\t\t}\n\t}\n}\n\nfunc TestNestedGroups(t *testing.T) {\n\thandlerPrintCounter := func(w http.ResponseWriter, r *http.Request) {\n\t\tcounter, _ := r.Context().Value(ctxKey{\"counter\"}).(int)\n\t\tw.Write([]byte(fmt.Sprintf(\"%v\", counter)))\n\t}\n\n\tmwIncreaseCounter := func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\tcounter, _ := ctx.Value(ctxKey{\"counter\"}).(int)\n\t\t\tcounter++\n\t\t\tctx = context.WithValue(ctx, ctxKey{\"counter\"}, counter)\n\t\t\tnext.ServeHTTP(w, r.WithContext(ctx))\n\t\t})\n\t}\n\n\t// Each route represents value of its counter (number of applied middlewares).\n\tr := NewRouter() // counter == 0\n\tr.Get(\"/0\", handlerPrintCounter)\n\tr.Group(func(r Router) {\n\t\tr.Use(mwIncreaseCounter) // counter == 1\n\t\tr.Get(\"/1\", handlerPrintCounter)\n\n\t\t// r.Handle(GET, \"/2\", Chain(mwIncreaseCounter).HandlerFunc(handlerPrintCounter))\n\t\tr.With(mwIncreaseCounter).Get(\"/2\", handlerPrintCounter)\n\n\t\tr.Group(func(r Router) {\n\t\t\tr.Use(mwIncreaseCounter, mwIncreaseCounter) // counter == 3\n\t\t\tr.Get(\"/3\", handlerPrintCounter)\n\t\t})\n\t\tr.Route(\"/\", func(r Router) {\n\t\t\tr.Use(mwIncreaseCounter, mwIncreaseCounter) // counter == 3\n\n\t\t\t// r.Handle(GET, \"/4\", Chain(mwIncreaseCounter).HandlerFunc(handlerPrintCounter))\n\t\t\tr.With(mwIncreaseCounter).Get(\"/4\", handlerPrintCounter)\n\n\t\t\tr.Group(func(r Router) {\n\t\t\t\tr.Use(mwIncreaseCounter, mwIncreaseCounter) // counter == 5\n\t\t\t\tr.Get(\"/5\", handlerPrintCounter)\n\t\t\t\t// r.Handle(GET, \"/6\", Chain(mwIncreaseCounter).HandlerFunc(handlerPrintCounter))\n\t\t\t\tr.With(mwIncreaseCounter).Get(\"/6\", handlerPrintCounter)\n\n\t\t\t})\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tfor _, route := range []string{\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\"} {\n\t\tif _, body := testRequest(t, ts, \"GET\", \"/\"+route, nil); body != route {\n\t\t\tt.Errorf(\"expected %v, got %v\", route, body)\n\t\t}\n\t}\n}\n\nfunc TestMiddlewarePanicOnLateUse(t *testing.T) {\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"hello\\n\"))\n\t}\n\n\tmw := func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tnext.ServeHTTP(w, r)\n\t\t})\n\t}\n\n\tdefer func() {\n\t\tif recover() == nil {\n\t\t\tt.Error(\"expected panic()\")\n\t\t}\n\t}()\n\n\tr := NewRouter()\n\tr.Get(\"/\", handler)\n\tr.Use(mw) // Too late to apply middleware, we're expecting panic().\n}\n\nfunc TestMountingExistingPath(t *testing.T) {\n\thandler := func(w http.ResponseWriter, r *http.Request) {}\n\n\tdefer func() {\n\t\tif recover() == nil {\n\t\t\tt.Error(\"expected panic()\")\n\t\t}\n\t}()\n\n\tr := NewRouter()\n\tr.Get(\"/\", handler)\n\tr.Mount(\"/hi\", http.HandlerFunc(handler))\n\tr.Mount(\"/hi\", http.HandlerFunc(handler))\n}\n\nfunc TestMountingSimilarPattern(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"bye\"))\n\t})\n\n\tr2 := NewRouter()\n\tr2.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"foobar\"))\n\t})\n\n\tr3 := NewRouter()\n\tr3.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"foo\"))\n\t})\n\n\tr.Mount(\"/foobar\", r2)\n\tr.Mount(\"/foo\", r3)\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/hi\", nil); body != \"bye\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxEmptyParams(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(`/users/{x}/{y}/{z}`, func(w http.ResponseWriter, r *http.Request) {\n\t\tx := URLParam(r, \"x\")\n\t\ty := URLParam(r, \"y\")\n\t\tz := URLParam(r, \"z\")\n\t\tw.Write([]byte(fmt.Sprintf(\"%s-%s-%s\", x, y, z)))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/users/a/b/c\", nil); body != \"a-b-c\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/users///c\", nil); body != \"--c\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxMissingParams(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(`/user/{userId:\\d+}`, func(w http.ResponseWriter, r *http.Request) {\n\t\tuserID := URLParam(r, \"userId\")\n\t\tw.Write([]byte(fmt.Sprintf(\"userId = '%s'\", userID)))\n\t})\n\tr.NotFound(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(404)\n\t\tw.Write([]byte(\"nothing here\"))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/user/123\", nil); body != \"userId = '123'\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/user/\", nil); body != \"nothing here\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxWildcardRoute(t *testing.T) {\n\thandler := func(w http.ResponseWriter, r *http.Request) {}\n\n\tdefer func() {\n\t\tif recover() == nil {\n\t\t\tt.Error(\"expected panic()\")\n\t\t}\n\t}()\n\n\tr := NewRouter()\n\tr.Get(\"/*/wildcard/must/be/at/end\", handler)\n}\n\nfunc TestMuxWildcardRouteCheckTwo(t *testing.T) {\n\thandler := func(w http.ResponseWriter, r *http.Request) {}\n\n\tdefer func() {\n\t\tif recover() == nil {\n\t\t\tt.Error(\"expected panic()\")\n\t\t}\n\t}()\n\n\tr := NewRouter()\n\tr.Get(\"/*/wildcard/{must}/be/at/end\", handler)\n}\n\nfunc TestMuxRegexp(t *testing.T) {\n\tr := NewRouter()\n\tr.Route(\"/{param:[0-9]*}/test\", func(r Router) {\n\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(fmt.Sprintf(\"Hi: %s\", URLParam(r, \"param\"))))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"//test\", nil); body != \"Hi: \" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxRegexp2(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/foo-{suffix:[a-z]{2,3}}.json\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(URLParam(r, \"suffix\")))\n\t})\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/foo-.json\", nil); body != \"\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/foo-abc.json\", nil); body != \"abc\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxRegexp3(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/one/{firstId:[a-z0-9-]+}/{secondId:[a-z]+}/first\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"first\"))\n\t})\n\tr.Get(\"/one/{firstId:[a-z0-9-_]+}/{secondId:[0-9]+}/second\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"second\"))\n\t})\n\tr.Delete(\"/one/{firstId:[a-z0-9-_]+}/{secondId:[0-9]+}/second\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"third\"))\n\t})\n\n\tr.Route(\"/one\", func(r Router) {\n\t\tr.Get(\"/{dns:[a-z-0-9_]+}\", func(writer http.ResponseWriter, request *http.Request) {\n\t\t\twriter.Write([]byte(\"_\"))\n\t\t})\n\t\tr.Get(\"/{dns:[a-z-0-9_]+}/info\", func(writer http.ResponseWriter, request *http.Request) {\n\t\t\twriter.Write([]byte(\"_\"))\n\t\t})\n\t\tr.Delete(\"/{id:[0-9]+}\", func(writer http.ResponseWriter, request *http.Request) {\n\t\t\twriter.Write([]byte(\"forth\"))\n\t\t})\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/one/hello/peter/first\", nil); body != \"first\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/one/hithere/123/second\", nil); body != \"second\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"DELETE\", \"/one/hithere/123/second\", nil); body != \"third\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"DELETE\", \"/one/123\", nil); body != \"forth\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxSubrouterWildcardParam(t *testing.T) {\n\th := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"param:%v *:%v\", URLParam(r, \"param\"), URLParam(r, \"*\"))\n\t})\n\n\tr := NewRouter()\n\n\tr.Get(\"/bare/{param}\", h)\n\tr.Get(\"/bare/{param}/*\", h)\n\n\tr.Route(\"/case0\", func(r Router) {\n\t\tr.Get(\"/{param}\", h)\n\t\tr.Get(\"/{param}/*\", h)\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/bare/hi\", nil); body != \"param:hi *:\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/bare/hi/yes\", nil); body != \"param:hi *:yes\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/case0/hi\", nil); body != \"param:hi *:\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"GET\", \"/case0/hi/yes\", nil); body != \"param:hi *:yes\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestMuxContextIsThreadSafe(t *testing.T) {\n\trouter := NewRouter()\n\trouter.Get(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\tctx, cancel := context.WithTimeout(r.Context(), 1*time.Millisecond)\n\t\tdefer cancel()\n\n\t\t<-ctx.Done()\n\t})\n\n\twg := sync.WaitGroup{}\n\n\tfor range 100 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor range 10000 {\n\t\t\t\tw := httptest.NewRecorder()\n\t\t\t\tr, err := http.NewRequest(\"GET\", \"/ok\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tctx, cancel := context.WithCancel(r.Context())\n\t\t\t\tr = r.WithContext(ctx)\n\n\t\t\t\tgo func() {\n\t\t\t\t\tcancel()\n\t\t\t\t}()\n\t\t\t\trouter.ServeHTTP(w, r)\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc TestEscapedURLParams(t *testing.T) {\n\tm := NewRouter()\n\tm.Get(\"/api/{identifier}/{region}/{size}/{rotation}/*\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(200)\n\t\trctx := RouteContext(r.Context())\n\t\tif rctx == nil {\n\t\t\tt.Error(\"no context\")\n\t\t\treturn\n\t\t}\n\t\tidentifier := URLParam(r, \"identifier\")\n\t\tif identifier != \"http:%2f%2fexample.com%2fimage.png\" {\n\t\t\tt.Errorf(\"identifier path parameter incorrect %s\", identifier)\n\t\t\treturn\n\t\t}\n\t\tregion := URLParam(r, \"region\")\n\t\tif region != \"full\" {\n\t\t\tt.Errorf(\"region path parameter incorrect %s\", region)\n\t\t\treturn\n\t\t}\n\t\tsize := URLParam(r, \"size\")\n\t\tif size != \"max\" {\n\t\t\tt.Errorf(\"size path parameter incorrect %s\", size)\n\t\t\treturn\n\t\t}\n\t\trotation := URLParam(r, \"rotation\")\n\t\tif rotation != \"0\" {\n\t\t\tt.Errorf(\"rotation path parameter incorrect %s\", rotation)\n\t\t\treturn\n\t\t}\n\t\tw.Write([]byte(\"success\"))\n\t})\n\n\tts := httptest.NewServer(m)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/api/http:%2f%2fexample.com%2fimage.png/full/max/0/color.png\", nil); body != \"success\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc TestCustomHTTPMethod(t *testing.T) {\n\t// first we must register this method to be accepted, then we\n\t// can define method handlers on the router below\n\tRegisterMethod(\"BOO\")\n\n\tr := NewRouter()\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\".\"))\n\t})\n\n\t// note the custom BOO method for route /hi\n\tr.MethodFunc(\"BOO\", \"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"custom method\"))\n\t})\n\n\tts := httptest.NewServer(r)\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/\", nil); body != \".\" {\n\t\tt.Fatal(body)\n\t}\n\tif _, body := testRequest(t, ts, \"BOO\", \"/hi\", nil); body != \"custom method\" {\n\t\tt.Fatal(body)\n\t}\n\n\tvar expectRoutes = map[string]string{\n\t\t\"GET\": \"/\",\n\t\t\"BOO\": \"/hi\",\n\t}\n\tWalk(r, func(method string, route string, handler http.Handler, _ ...func(http.Handler) http.Handler) error {\n\t\tr, ok := expectRoutes[method]\n\t\tif !ok {\n\t\t\tt.Fatalf(\"unexpected method %s\", method)\n\t\t}\n\t\tif r != route {\n\t\t\tt.Fatalf(\"expected route %s, got %s\", r, route)\n\t\t}\n\t\tdelete(expectRoutes, method)\n\n\t\treturn nil\n\t})\n\tif len(expectRoutes) != 0 {\n\t\tt.Fatalf(\"missing expected methods: %v\", expectRoutes)\n\t}\n}\n\nfunc TestMuxMatch(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Test\", \"yes\")\n\t\tw.Write([]byte(\"bye\"))\n\t})\n\tr.Route(\"/articles\", func(r Router) {\n\t\tr.Get(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tid := URLParam(r, \"id\")\n\t\t\tw.Header().Set(\"X-Article\", id)\n\t\t\tw.Write([]byte(\"article:\" + id))\n\t\t})\n\t})\n\tr.Route(\"/users\", func(r Router) {\n\t\tr.Head(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Header().Set(\"X-User\", \"-\")\n\t\t\tw.Write([]byte(\"user\"))\n\t\t})\n\t\tr.Get(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tid := URLParam(r, \"id\")\n\t\t\tw.Header().Set(\"X-User\", id)\n\t\t\tw.Write([]byte(\"user:\" + id))\n\t\t})\n\t})\n\n\ttctx := NewRouteContext()\n\n\ttctx.Reset()\n\tif r.Match(tctx, \"GET\", \"/users/1\") == false {\n\t\tt.Fatal(\"expecting to find match for route:\", \"GET\", \"/users/1\")\n\t}\n\n\ttctx.Reset()\n\tif r.Match(tctx, \"HEAD\", \"/articles/10\") == true {\n\t\tt.Fatal(\"not expecting to find match for route:\", \"HEAD\", \"/articles/10\")\n\t}\n}\n\nfunc TestMuxMatch_HasBasePath(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Test\", \"yes\")\n\t\tw.Write([]byte(\"\"))\n\t})\n\n\ttctx := NewRouteContext()\n\n\ttctx.Reset()\n\tif r.Match(tctx, \"GET\", \"/\") != true {\n\t\tt.Fatal(\"expecting to find match for route:\", \"GET\", \"/\")\n\t}\n}\n\nfunc TestMuxMatch_DoesNotHaveBasePath(t *testing.T) {\n\tr := NewRouter()\n\n\ttctx := NewRouteContext()\n\n\ttctx.Reset()\n\tif r.Match(tctx, \"GET\", \"/\") != false {\n\t\tt.Fatal(\"not expecting to find match for route:\", \"GET\", \"/\")\n\t}\n}\n\nfunc TestMuxFind(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Test\", \"yes\")\n\t\tw.Write([]byte(\"\"))\n\t})\n\tr.Get(\"/hi\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Test\", \"yes\")\n\t\tw.Write([]byte(\"bye\"))\n\t})\n\tr.Route(\"/yo\", func(r Router) {\n\t\tr.Get(\"/sup\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Write([]byte(\"sup\"))\n\t\t})\n\t})\n\tr.Route(\"/articles\", func(r Router) {\n\t\tr.Get(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tid := URLParam(r, \"id\")\n\t\t\tw.Header().Set(\"X-Article\", id)\n\t\t\tw.Write([]byte(\"article:\" + id))\n\t\t})\n\t})\n\tr.Route(\"/users\", func(r Router) {\n\t\tr.Head(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Header().Set(\"X-User\", \"-\")\n\t\t\tw.Write([]byte(\"user\"))\n\t\t})\n\t\tr.Get(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tid := URLParam(r, \"id\")\n\t\t\tw.Header().Set(\"X-User\", id)\n\t\t\tw.Write([]byte(\"user:\" + id))\n\t\t})\n\t})\n\tr.Route(\"/api\", func(r Router) {\n\t\tr.Route(\"/groups\", func(r Router) {\n\t\t\tr.Route(\"/v2\", func(r Router) {\n\t\t\t\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tw.Write([]byte(\"groups\"))\n\t\t\t\t})\n\t\t\t\tr.Post(\"/{id}\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tw.Write([]byte(\"POST groups\"))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\ttctx := NewRouteContext()\n\n\ttctx.Reset()\n\tif r.Find(tctx, \"GET\", \"\") == \"/\" {\n\t\tt.Fatal(\"expecting to find pattern / for route: GET\")\n\t}\n\n\ttctx.Reset()\n\tif r.Find(tctx, \"GET\", \"/nope\") != \"\" {\n\t\tt.Fatal(\"not expecting to find pattern for route: GET /nope\")\n\t}\n\n\ttctx.Reset()\n\tif r.Find(tctx, \"GET\", \"/users/1\") != \"/users/{id}\" {\n\t\tt.Fatal(\"expecting to find pattern /users/{id} for route: GET /users/1\")\n\t}\n\n\ttctx.Reset()\n\tif r.Find(tctx, \"HEAD\", \"/articles/10\") != \"\" {\n\t\tt.Fatal(\"not expecting to find pattern for route: HEAD /articles/10\")\n\t}\n\n\ttctx.Reset()\n\tif r.Find(tctx, \"GET\", \"/yo/sup\") != \"/yo/sup\" {\n\t\tt.Fatal(\"expecting to find pattern /yo/sup for route: GET /yo/sup\")\n\t}\n\n\ttctx.Reset()\n\tif r.Find(tctx, \"GET\", \"/api/groups/v2/\") != \"/api/groups/v2/\" {\n\t\tt.Fatal(\"expecting to find pattern /api/groups/v2/ for route: GET /api/groups/v2/\")\n\t}\n\n\ttctx.Reset()\n\tif r.Find(tctx, \"POST\", \"/api/groups/v2/1\") != \"/api/groups/v2/{id}\" {\n\t\tt.Fatal(\"expecting to find pattern /api/groups/v2/{id} for route: POST /api/groups/v2/1\")\n\t}\n}\n\nfunc TestServerBaseContext(t *testing.T) {\n\tr := NewRouter()\n\tr.Get(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tbaseYes := r.Context().Value(ctxKey{\"base\"}).(string)\n\t\tif _, ok := r.Context().Value(http.ServerContextKey).(*http.Server); !ok {\n\t\t\tpanic(\"missing server context\")\n\t\t}\n\t\tif _, ok := r.Context().Value(http.LocalAddrContextKey).(net.Addr); !ok {\n\t\t\tpanic(\"missing local addr context\")\n\t\t}\n\t\tw.Write([]byte(baseYes))\n\t})\n\n\t// Setup http Server with a base context\n\tctx := context.WithValue(context.Background(), ctxKey{\"base\"}, \"yes\")\n\tts := httptest.NewUnstartedServer(r)\n\tts.Config.BaseContext = func(_ net.Listener) context.Context {\n\t\treturn ctx\n\t}\n\tts.Start()\n\n\tdefer ts.Close()\n\n\tif _, body := testRequest(t, ts, \"GET\", \"/\", nil); body != \"yes\" {\n\t\tt.Fatal(body)\n\t}\n}\n\nfunc testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) {\n\treq, err := http.NewRequest(method, ts.URL+path, body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil, \"\"\n\t}\n\tdefer resp.Body.Close()\n\n\treturn resp, string(respBody)\n}\n\nfunc testHandler(t *testing.T, h http.Handler, method, path string, body io.Reader) (*http.Response, string) {\n\tr, _ := http.NewRequest(method, path, body)\n\tw := httptest.NewRecorder()\n\th.ServeHTTP(w, r)\n\treturn w.Result(), w.Body.String()\n}\n\ntype ctxKey struct {\n\tname string\n}\n\nfunc (k ctxKey) String() string {\n\treturn \"context value \" + k.name\n}\n\nfunc BenchmarkMux(b *testing.B) {\n\th1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\th2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\th3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\th4 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\th5 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\th6 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\n\tmx := NewRouter()\n\tmx.Get(\"/\", h1)\n\tmx.Get(\"/hi\", h2)\n\tmx.Post(\"/hi-post\", h2) // used to benchmark 405 responses\n\tmx.Get(\"/sup/{id}/and/{this}\", h3)\n\tmx.Get(\"/sup/{id}/{bar:foo}/{this}\", h3)\n\n\tmx.Route(\"/sharing/{x}/{hash}\", func(mx Router) {\n\t\tmx.Get(\"/\", h4)          // subrouter-1\n\t\tmx.Get(\"/{network}\", h5) // subrouter-1\n\t\tmx.Get(\"/twitter\", h5)\n\t\tmx.Route(\"/direct\", func(mx Router) {\n\t\t\tmx.Get(\"/\", h6) // subrouter-2\n\t\t\tmx.Get(\"/download\", h6)\n\t\t})\n\t})\n\n\troutes := []string{\n\t\t\"/\",\n\t\t\"/hi\",\n\t\t\"/hi-post\",\n\t\t\"/sup/123/and/this\",\n\t\t\"/sup/123/foo/this\",\n\t\t\"/sharing/z/aBc\",                 // subrouter-1\n\t\t\"/sharing/z/aBc/twitter\",         // subrouter-1\n\t\t\"/sharing/z/aBc/direct\",          // subrouter-2\n\t\t\"/sharing/z/aBc/direct/download\", // subrouter-2\n\t}\n\n\tfor _, path := range routes {\n\t\tb.Run(\"route:\"+path, func(b *testing.B) {\n\t\t\tw := httptest.NewRecorder()\n\t\t\tr, _ := http.NewRequest(\"GET\", path, nil)\n\n\t\t\tb.ReportAllocs()\n\t\t\tb.ResetTimer()\n\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tmx.ServeHTTP(w, r)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "path_value_test.go",
    "content": "package chi\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPathValue(t *testing.T) {\n\ttestCases := []struct {\n\t\tname         string\n\t\tpattern      string\n\t\tmethod       string\n\t\trequestPath  string\n\t\texpectedBody string\n\t\tpathKeys     []string\n\t}{\n\t\t{\n\t\t\tname:         \"Basic path value\",\n\t\t\tpattern:      \"/hubs/{hubID}\",\n\t\t\tmethod:       \"GET\",\n\t\t\tpathKeys:     []string{\"hubID\"},\n\t\t\trequestPath:  \"/hubs/392\",\n\t\t\texpectedBody: \"392\",\n\t\t},\n\t\t{\n\t\t\tname:         \"Two path values\",\n\t\t\tpattern:      \"/users/{userID}/conversations/{conversationID}\",\n\t\t\tmethod:       \"POST\",\n\t\t\tpathKeys:     []string{\"userID\", \"conversationID\"},\n\t\t\trequestPath:  \"/users/Gojo/conversations/2948\",\n\t\t\texpectedBody: \"Gojo 2948\",\n\t\t},\n\t\t{\n\t\t\tname:         \"Wildcard path\",\n\t\t\tpattern:      \"/users/{userID}/friends/*\",\n\t\t\tmethod:       \"POST\",\n\t\t\tpathKeys:     []string{\"userID\", \"*\"},\n\t\t\trequestPath:  \"/users/Gojo/friends/all-of-them/and/more\",\n\t\t\texpectedBody: \"Gojo all-of-them/and/more\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tr := NewRouter()\n\n\t\t\tr.Handle(tc.method+\" \"+tc.pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tpathValues := []string{}\n\t\t\t\tfor _, pathKey := range tc.pathKeys {\n\t\t\t\t\tpathValue := r.PathValue(pathKey)\n\t\t\t\t\tif pathValue == \"\" {\n\t\t\t\t\t\tpathValue = \"NOT_FOUND:\" + pathKey\n\t\t\t\t\t}\n\n\t\t\t\t\tpathValues = append(pathValues, pathValue)\n\t\t\t\t}\n\n\t\t\t\tbody := strings.Join(pathValues, \" \")\n\n\t\t\t\tw.Write([]byte(body))\n\t\t\t}))\n\n\t\t\tts := httptest.NewServer(r)\n\t\t\tdefer ts.Close()\n\n\t\t\t_, body := testRequest(t, ts, tc.method, tc.requestPath, nil)\n\t\t\tif body != tc.expectedBody {\n\t\t\t\tt.Fatalf(\"expecting %q, got %q\", tc.expectedBody, body)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pattern_test.go",
    "content": "//go:build go1.23\n// +build go1.23\n\npackage chi\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestPattern(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\tpattern     string\n\t\tmethod      string\n\t\trequestPath string\n\t}{\n\t\t{\n\t\t\tname:        \"Basic path value\",\n\t\t\tpattern:     \"/hubs/{hubID}\",\n\t\t\tmethod:      \"GET\",\n\t\t\trequestPath: \"/hubs/392\",\n\t\t},\n\t\t{\n\t\t\tname:        \"Two path values\",\n\t\t\tpattern:     \"/users/{userID}/conversations/{conversationID}\",\n\t\t\tmethod:      \"POST\",\n\t\t\trequestPath: \"/users/Gojo/conversations/2948\",\n\t\t},\n\t\t{\n\t\t\tname:        \"Wildcard path\",\n\t\t\tpattern:     \"/users/{userID}/friends/*\",\n\t\t\tmethod:      \"POST\",\n\t\t\trequestPath: \"/users/Gojo/friends/all-of-them/and/more\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tr := NewRouter()\n\n\t\t\tr.Handle(tc.method+\" \"+tc.pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.Write([]byte(r.Pattern))\n\t\t\t}))\n\n\t\t\tts := httptest.NewServer(r)\n\t\t\tdefer ts.Close()\n\n\t\t\t_, body := testRequest(t, ts, tc.method, tc.requestPath, nil)\n\t\t\tif body != tc.pattern {\n\t\t\t\tt.Fatalf(\"expecting %q, got %q\", tc.pattern, body)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "testdata/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC/zCCAeegAwIBAgIRANioW0Re7DtpT4qZpJU1iK8wDQYJKoZIhvcNAQELBQAw\nEjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjEyMzExNDU0MzBaFw0xNzEyMzExNDU0\nMzBaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDpFfOsaXDYlL+ektfsqGYrSAsoTbe7zqjpow9nqUU4PmLRu2YMaaW8\nfAoneUnJxsJw7ql38+VMpphZUOmOWvsO7uV/lfnTIQfTwllHDdgAR5A11d84Zy/y\nTiNIFJduuaPtEhQs1dxPhU7TG8sEfFRhBoUDPv473akeGPNkVU756RVBYM6rUc3b\nYygD0PXGsQ2obrImbYUyyHH5YClCvGl1No57n3ugLqSSfwbgR3/Gw7kkGKy0PMOu\nTuHuJnTEmofJPkqEyFRVMlIAtfqFqJUfDHTOuQGWIUPnjDg+fqTI9EPJ+pElBqDQ\nIqW93BY5XePMdrTQc1h6xkduDfuLeA7TAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIF\noDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBkGA1UdEQQSMBCC\nDmxvY2FsaG9zdDo3MDcyMA0GCSqGSIb3DQEBCwUAA4IBAQDnsWmZdf7209A/XHUe\nxoONCbU8jaYFVoA+CN9J+3CASzrzTQ4fh9RJdm2FZuv4sWnb5c5hDN7H/M/nLcb0\n+uu7ACBGhd7yACYCQm/z3Pm3CY2BRIo0vCCRioGx+6J3CPGWFm0vHwNBge0iBOKC\nWn+/YOlTDth/M3auHYlr7hdFmf57U4V/5iTr4wiKxwM9yMPcVRQF/1XpPd7A0VqM\nnFSEfDpFjrA7MvT3DrRqQGqF/ZXxDbro2nyki3YG8FwgKlFNVN9w55zNiriQ+WNA\nuz86lKg1FTc+m/R/0CD//7+7mme28N813EPVdV83TgxWNrfvAIRazkHE7YxETry0\nBJDg\n-----END CERTIFICATE-----"
  },
  {
    "path": "testdata/key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA6RXzrGlw2JS/npLX7KhmK0gLKE23u86o6aMPZ6lFOD5i0btm\nDGmlvHwKJ3lJycbCcO6pd/PlTKaYWVDpjlr7Du7lf5X50yEH08JZRw3YAEeQNdXf\nOGcv8k4jSBSXbrmj7RIULNXcT4VO0xvLBHxUYQaFAz7+O92pHhjzZFVO+ekVQWDO\nq1HN22MoA9D1xrENqG6yJm2FMshx+WApQrxpdTaOe597oC6kkn8G4Ed/xsO5JBis\ntDzDrk7h7iZ0xJqHyT5KhMhUVTJSALX6haiVHwx0zrkBliFD54w4Pn6kyPRDyfqR\nJQag0CKlvdwWOV3jzHa00HNYesZHbg37i3gO0wIDAQABAoIBAFvqYDE5U1rVLctm\ntOeKcN/YhS3bl/zjvhCEUOrcAYPwdh+m+tMiRk1RzN9MISEE1GCcfQ/kiiPz/lga\nZD/S+PYmlzH8/ouXlvKWzYYLm4ZgsinIsUIYzvuKfLdMB3uOkWpHmtUjcMGbHD57\n009tiAjK/WEOUkthWfOYe0KxsXczBn3PTAWZuiIkuA3RVWa7pCCFHUENkViP58wl\nKy1hYKnunKPApRwuiC6qIT5ZOCSukdCCbkmRnj/x+P8+nsosu+1d85MNZb8uLRi0\nRzMmuOfOK2poDsrNHQX7itKlu7rzMJQc3+RauqIZovNe/BmSq+tYBLboXvUp18g/\n+VqKeEECgYEA/LaD1tJepzD/1lhgunFcnDjxsDJqLUpfR5eDMX1qhGJphuPBLOXS\nushmVVjbVIn25Wxeoe4RYrZ6Tuu0FEJJgV44Lt42OOFgK2gyrCJpYmlxpRaw+7jc\nDbp1Sh3/9VqMZjR/mQIzTnfOtS2n4Fk1Q53hdJn5Pn+uPMmMO4hF87sCgYEA7B4V\nBACsd6eqVxKkEMc72VLeYb0Ri0bl0FwbvIKXImppwA0tbMDmeA+6yhcRm23dhd5v\ncfNhJepRIzkM2CkhnazlsAbDoJPqb7/sbNzodtW1P0op7YIFYbrkcX4yOu9O1DNI\nIj4PR8H1WcpPjhvr3q+iNO5agQX7bMQ1BnnJg8kCgYBA1tdm090DSrgpl81hqNpZ\nHucsDRNfAXkG1mIL3aDpzJJE0MTsrx7tW6Od/ElyHF/jp3V0WK/PQwCIpUMz+3n+\nnl0N8We6GmFhYb+2mLGvVVyaPgM04s5bG18ioCXfHtdtFcUzTfQ6CtVXeRpcnqbi\n7Ww+TY88sOfUouW/FIzWJwKBgQCsLauJhaw+fOc8I328NmywJzu+7g5TD9oZvHEF\nX/0xvYNr5rAPNANb3ayKHZRbURxOuEtwPtfCvEF6e+mf3y6COkgrumMBP5ue7cdM\nAzMJJQHMKxqz9TJTd+OJ10ptq4BCQTsCrVqbKxbs6RhmOnofoteX3Y/lsiULxXAd\nTsXh8QKBgQDQHosH8VoL7vIK+SqY5uoHAhMytSVNx4IaZZg4ho8oyjw12QXcidgV\nQJZQMdPEv8cAK78WcQdSthop+O/tu2cKLHyAmWmO3oU7gIQECui0aMXSqraO6Vde\nC5tqYlyLa7bHZS3AqrjRv9BRfwPKVkmBoYdA652rN/tE/K4UWsghnA==\n-----END RSA PRIVATE KEY-----"
  },
  {
    "path": "tree.go",
    "content": "package chi\n\n// Radix tree implementation below is a based on the original work by\n// Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go\n// (MIT licensed). It's been heavily modified for use as a HTTP routing tree.\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype methodTyp uint\n\nconst (\n\tmSTUB methodTyp = 1 << iota\n\tmCONNECT\n\tmDELETE\n\tmGET\n\tmHEAD\n\tmOPTIONS\n\tmPATCH\n\tmPOST\n\tmPUT\n\tmTRACE\n)\n\nvar mALL = mCONNECT | mDELETE | mGET | mHEAD |\n\tmOPTIONS | mPATCH | mPOST | mPUT | mTRACE\n\nvar methodMap = map[string]methodTyp{\n\thttp.MethodConnect: mCONNECT,\n\thttp.MethodDelete:  mDELETE,\n\thttp.MethodGet:     mGET,\n\thttp.MethodHead:    mHEAD,\n\thttp.MethodOptions: mOPTIONS,\n\thttp.MethodPatch:   mPATCH,\n\thttp.MethodPost:    mPOST,\n\thttp.MethodPut:     mPUT,\n\thttp.MethodTrace:   mTRACE,\n}\n\nvar reverseMethodMap = map[methodTyp]string{\n\tmCONNECT: http.MethodConnect,\n\tmDELETE:  http.MethodDelete,\n\tmGET:     http.MethodGet,\n\tmHEAD:    http.MethodHead,\n\tmOPTIONS: http.MethodOptions,\n\tmPATCH:   http.MethodPatch,\n\tmPOST:    http.MethodPost,\n\tmPUT:     http.MethodPut,\n\tmTRACE:   http.MethodTrace,\n}\n\n// RegisterMethod adds support for custom HTTP method handlers, available\n// via Router#Method and Router#MethodFunc\nfunc RegisterMethod(method string) {\n\tif method == \"\" {\n\t\treturn\n\t}\n\tmethod = strings.ToUpper(method)\n\tif _, ok := methodMap[method]; ok {\n\t\treturn\n\t}\n\tn := len(methodMap)\n\tif n > strconv.IntSize-2 {\n\t\tpanic(fmt.Sprintf(\"chi: max number of methods reached (%d)\", strconv.IntSize))\n\t}\n\tmt := methodTyp(2 << n)\n\tmethodMap[method] = mt\n\treverseMethodMap[mt] = method\n\tmALL |= mt\n}\n\ntype nodeTyp uint8\n\nconst (\n\tntStatic   nodeTyp = iota // /home\n\tntRegexp                  // /{id:[0-9]+}\n\tntParam                   // /{user}\n\tntCatchAll                // /api/v1/*\n)\n\ntype node struct {\n\t// subroutes on the leaf node\n\tsubroutes Routes\n\n\t// regexp matcher for regexp nodes\n\trex *regexp.Regexp\n\n\t// HTTP handler endpoints on the leaf node\n\tendpoints endpoints\n\n\t// prefix is the common prefix we ignore\n\tprefix string\n\n\t// child nodes should be stored in-order for iteration,\n\t// in groups of the node type.\n\tchildren [ntCatchAll + 1]nodes\n\n\t// first byte of the child prefix\n\ttail byte\n\n\t// node type: static, regexp, param, catchAll\n\ttyp nodeTyp\n\n\t// first byte of the prefix\n\tlabel byte\n}\n\n// endpoints is a mapping of http method constants to handlers\n// for a given route.\ntype endpoints map[methodTyp]*endpoint\n\ntype endpoint struct {\n\t// endpoint handler\n\thandler http.Handler\n\n\t// pattern is the routing pattern for handler nodes\n\tpattern string\n\n\t// parameter keys recorded on handler nodes\n\tparamKeys []string\n}\n\nfunc (s endpoints) Value(method methodTyp) *endpoint {\n\tmh, ok := s[method]\n\tif !ok {\n\t\tmh = &endpoint{}\n\t\ts[method] = mh\n\t}\n\treturn mh\n}\n\nfunc (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node {\n\tvar parent *node\n\tsearch := pattern\n\n\tfor {\n\t\t// Handle key exhaustion\n\t\tif len(search) == 0 {\n\t\t\t// Insert or update the node's leaf handler\n\t\t\tn.setEndpoint(method, handler, pattern)\n\t\t\treturn n\n\t\t}\n\n\t\t// We're going to be searching for a wild node next,\n\t\t// in this case, we need to get the tail\n\t\tvar label = search[0]\n\t\tvar segTail byte\n\t\tvar segEndIdx int\n\t\tvar segTyp nodeTyp\n\t\tvar segRexpat string\n\t\tif label == '{' || label == '*' {\n\t\t\tsegTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search)\n\t\t}\n\n\t\tvar prefix string\n\t\tif segTyp == ntRegexp {\n\t\t\tprefix = segRexpat\n\t\t}\n\n\t\t// Look for the edge to attach to\n\t\tparent = n\n\t\tn = n.getEdge(segTyp, label, segTail, prefix)\n\n\t\t// No edge, create one\n\t\tif n == nil {\n\t\t\tchild := &node{label: label, tail: segTail, prefix: search}\n\t\t\thn := parent.addChild(child, search)\n\t\t\thn.setEndpoint(method, handler, pattern)\n\n\t\t\treturn hn\n\t\t}\n\n\t\t// Found an edge to match the pattern\n\n\t\tif n.typ > ntStatic {\n\t\t\t// We found a param node, trim the param from the search path and continue.\n\t\t\t// This param/wild pattern segment would already be on the tree from a previous\n\t\t\t// call to addChild when creating a new node.\n\t\t\tsearch = search[segEndIdx:]\n\t\t\tcontinue\n\t\t}\n\n\t\t// Static nodes fall below here.\n\t\t// Determine longest prefix of the search key on match.\n\t\tcommonPrefix := longestPrefix(search, n.prefix)\n\t\tif commonPrefix == len(n.prefix) {\n\t\t\t// the common prefix is as long as the current node's prefix we're attempting to insert.\n\t\t\t// keep the search going.\n\t\t\tsearch = search[commonPrefix:]\n\t\t\tcontinue\n\t\t}\n\n\t\t// Split the node\n\t\tchild := &node{\n\t\t\ttyp:    ntStatic,\n\t\t\tprefix: search[:commonPrefix],\n\t\t}\n\t\tparent.replaceChild(search[0], segTail, child)\n\n\t\t// Restore the existing node\n\t\tn.label = n.prefix[commonPrefix]\n\t\tn.prefix = n.prefix[commonPrefix:]\n\t\tchild.addChild(n, n.prefix)\n\n\t\t// If the new key is a subset, set the method/handler on this node and finish.\n\t\tsearch = search[commonPrefix:]\n\t\tif len(search) == 0 {\n\t\t\tchild.setEndpoint(method, handler, pattern)\n\t\t\treturn child\n\t\t}\n\n\t\t// Create a new edge for the node\n\t\tsubchild := &node{\n\t\t\ttyp:    ntStatic,\n\t\t\tlabel:  search[0],\n\t\t\tprefix: search,\n\t\t}\n\t\thn := child.addChild(subchild, search)\n\t\thn.setEndpoint(method, handler, pattern)\n\t\treturn hn\n\t}\n}\n\n// addChild appends the new `child` node to the tree using the `pattern` as the trie key.\n// For a URL router like chi's, we split the static, param, regexp and wildcard segments\n// into different nodes. In addition, addChild will recursively call itself until every\n// pattern segment is added to the url pattern tree as individual nodes, depending on type.\nfunc (n *node) addChild(child *node, prefix string) *node {\n\tsearch := prefix\n\n\t// handler leaf node added to the tree is the child.\n\t// this may be overridden later down the flow\n\thn := child\n\n\t// Parse next segment\n\tsegTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search)\n\n\t// Add child depending on next up segment\n\tswitch segTyp {\n\n\tcase ntStatic:\n\t\t// Search prefix is all static (that is, has no params in path)\n\t\t// noop\n\n\tdefault:\n\t\t// Search prefix contains a param, regexp or wildcard\n\n\t\tif segTyp == ntRegexp {\n\t\t\trex, err := regexp.Compile(segRexpat)\n\t\t\tif err != nil {\n\t\t\t\tpanic(fmt.Sprintf(\"chi: invalid regexp pattern '%s' in route param\", segRexpat))\n\t\t\t}\n\t\t\tchild.prefix = segRexpat\n\t\t\tchild.rex = rex\n\t\t}\n\n\t\tif segStartIdx == 0 {\n\t\t\t// Route starts with a param\n\t\t\tchild.typ = segTyp\n\n\t\t\tif segTyp == ntCatchAll {\n\t\t\t\tsegStartIdx = -1\n\t\t\t} else {\n\t\t\t\tsegStartIdx = segEndIdx\n\t\t\t}\n\t\t\tif segStartIdx < 0 {\n\t\t\t\tsegStartIdx = len(search)\n\t\t\t}\n\t\t\tchild.tail = segTail // for params, we set the tail\n\n\t\t\tif segStartIdx != len(search) {\n\t\t\t\t// add static edge for the remaining part, split the end.\n\t\t\t\t// its not possible to have adjacent param nodes, so its certainly\n\t\t\t\t// going to be a static node next.\n\n\t\t\t\tsearch = search[segStartIdx:] // advance search position\n\n\t\t\t\tnn := &node{\n\t\t\t\t\ttyp:    ntStatic,\n\t\t\t\t\tlabel:  search[0],\n\t\t\t\t\tprefix: search,\n\t\t\t\t}\n\t\t\t\thn = child.addChild(nn, search)\n\t\t\t}\n\n\t\t} else if segStartIdx > 0 {\n\t\t\t// Route has some param\n\n\t\t\t// starts with a static segment\n\t\t\tchild.typ = ntStatic\n\t\t\tchild.prefix = search[:segStartIdx]\n\t\t\tchild.rex = nil\n\n\t\t\t// add the param edge node\n\t\t\tsearch = search[segStartIdx:]\n\n\t\t\tnn := &node{\n\t\t\t\ttyp:   segTyp,\n\t\t\t\tlabel: search[0],\n\t\t\t\ttail:  segTail,\n\t\t\t}\n\t\t\thn = child.addChild(nn, search)\n\n\t\t}\n\t}\n\n\tn.children[child.typ] = append(n.children[child.typ], child)\n\tn.children[child.typ].Sort()\n\treturn hn\n}\n\nfunc (n *node) replaceChild(label, tail byte, child *node) {\n\tfor i := 0; i < len(n.children[child.typ]); i++ {\n\t\tif n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail {\n\t\t\tn.children[child.typ][i] = child\n\t\t\tn.children[child.typ][i].label = label\n\t\t\tn.children[child.typ][i].tail = tail\n\t\t\treturn\n\t\t}\n\t}\n\tpanic(\"chi: replacing missing child\")\n}\n\nfunc (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node {\n\tnds := n.children[ntyp]\n\tfor i := range nds {\n\t\tif nds[i].label == label && nds[i].tail == tail {\n\t\t\tif ntyp == ntRegexp && nds[i].prefix != prefix {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nds[i]\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) {\n\t// Set the handler for the method type on the node\n\tif n.endpoints == nil {\n\t\tn.endpoints = make(endpoints)\n\t}\n\n\tparamKeys := patParamKeys(pattern)\n\n\tif method&mSTUB == mSTUB {\n\t\tn.endpoints.Value(mSTUB).handler = handler\n\t}\n\tif method&mALL == mALL {\n\t\th := n.endpoints.Value(mALL)\n\t\th.handler = handler\n\t\th.pattern = pattern\n\t\th.paramKeys = paramKeys\n\t\tfor _, m := range methodMap {\n\t\t\th := n.endpoints.Value(m)\n\t\t\th.handler = handler\n\t\t\th.pattern = pattern\n\t\t\th.paramKeys = paramKeys\n\t\t}\n\t} else {\n\t\th := n.endpoints.Value(method)\n\t\th.handler = handler\n\t\th.pattern = pattern\n\t\th.paramKeys = paramKeys\n\t}\n}\n\nfunc (n *node) FindRoute(rctx *Context, method methodTyp, path string) (*node, endpoints, http.Handler) {\n\t// Reset the context routing pattern and params\n\trctx.routePattern = \"\"\n\trctx.routeParams.Keys = rctx.routeParams.Keys[:0]\n\trctx.routeParams.Values = rctx.routeParams.Values[:0]\n\n\t// Find the routing handlers for the path\n\trn := n.findRoute(rctx, method, path)\n\tif rn == nil {\n\t\treturn nil, nil, nil\n\t}\n\n\t// Record the routing params in the request lifecycle\n\trctx.URLParams.Keys = append(rctx.URLParams.Keys, rctx.routeParams.Keys...)\n\trctx.URLParams.Values = append(rctx.URLParams.Values, rctx.routeParams.Values...)\n\n\t// Record the routing pattern in the request lifecycle\n\tif rn.endpoints[method].pattern != \"\" {\n\t\trctx.routePattern = rn.endpoints[method].pattern\n\t\trctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern)\n\t}\n\n\treturn rn, rn.endpoints, rn.endpoints[method].handler\n}\n\n// Recursive edge traversal by checking all nodeTyp groups along the way.\n// It's like searching through a multi-dimensional radix trie.\nfunc (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {\n\tnn := n\n\tsearch := path\n\n\tfor t, nds := range nn.children {\n\t\tntyp := nodeTyp(t)\n\t\tif len(nds) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar xn *node\n\t\txsearch := search\n\n\t\tvar label byte\n\t\tif search != \"\" {\n\t\t\tlabel = search[0]\n\t\t}\n\n\t\tswitch ntyp {\n\t\tcase ntStatic:\n\t\t\txn = nds.findEdge(label)\n\t\t\tif xn == nil || !strings.HasPrefix(xsearch, xn.prefix) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\txsearch = xsearch[len(xn.prefix):]\n\n\t\tcase ntParam, ntRegexp:\n\t\t\t// short-circuit and return no matching route for empty param values\n\t\t\tif xsearch == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// serially loop through each node grouped by the tail delimiter\n\t\t\tfor _, xn = range nds {\n\t\t\t\t// label for param nodes is the delimiter byte\n\t\t\t\tp := strings.IndexByte(xsearch, xn.tail)\n\n\t\t\t\tif p < 0 {\n\t\t\t\t\tif xn.tail == '/' {\n\t\t\t\t\t\tp = len(xsearch)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t} else if ntyp == ntRegexp && p == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif ntyp == ntRegexp && xn.rex != nil {\n\t\t\t\t\tif !xn.rex.MatchString(xsearch[:p]) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t} else if strings.IndexByte(xsearch[:p], '/') != -1 {\n\t\t\t\t\t// avoid a match across path segments\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tprevlen := len(rctx.routeParams.Values)\n\t\t\t\trctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p])\n\t\t\t\txsearch = xsearch[p:]\n\n\t\t\t\tif len(xsearch) == 0 {\n\t\t\t\t\tif xn.isLeaf() {\n\t\t\t\t\t\th := xn.endpoints[method]\n\t\t\t\t\t\tif h != nil && h.handler != nil {\n\t\t\t\t\t\t\trctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...)\n\t\t\t\t\t\t\treturn xn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor endpoints := range xn.endpoints {\n\t\t\t\t\t\t\tif endpoints == mALL || endpoints == mSTUB {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\trctx.methodsAllowed = append(rctx.methodsAllowed, endpoints)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// flag that the routing context found a route, but not a corresponding\n\t\t\t\t\t\t// supported method\n\t\t\t\t\t\trctx.methodNotAllowed = true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// recursively find the next node on this branch\n\t\t\t\tfin := xn.findRoute(rctx, method, xsearch)\n\t\t\t\tif fin != nil {\n\t\t\t\t\treturn fin\n\t\t\t\t}\n\n\t\t\t\t// not found on this branch, reset vars\n\t\t\t\trctx.routeParams.Values = rctx.routeParams.Values[:prevlen]\n\t\t\t\txsearch = search\n\t\t\t}\n\n\t\t\trctx.routeParams.Values = append(rctx.routeParams.Values, \"\")\n\n\t\tdefault:\n\t\t\t// catch-all nodes\n\t\t\trctx.routeParams.Values = append(rctx.routeParams.Values, search)\n\t\t\txn = nds[0]\n\t\t\txsearch = \"\"\n\t\t}\n\n\t\tif xn == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// did we find it yet?\n\t\tif len(xsearch) == 0 {\n\t\t\tif xn.isLeaf() {\n\t\t\t\th := xn.endpoints[method]\n\t\t\t\tif h != nil && h.handler != nil {\n\t\t\t\t\trctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...)\n\t\t\t\t\treturn xn\n\t\t\t\t}\n\n\t\t\t\tfor endpoints := range xn.endpoints {\n\t\t\t\t\tif endpoints == mALL || endpoints == mSTUB {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\trctx.methodsAllowed = append(rctx.methodsAllowed, endpoints)\n\t\t\t\t}\n\n\t\t\t\t// flag that the routing context found a route, but not a corresponding\n\t\t\t\t// supported method\n\t\t\t\trctx.methodNotAllowed = true\n\t\t\t}\n\t\t}\n\n\t\t// recursively find the next node..\n\t\tfin := xn.findRoute(rctx, method, xsearch)\n\t\tif fin != nil {\n\t\t\treturn fin\n\t\t}\n\n\t\t// Did not find final handler, let's remove the param here if it was set\n\t\tif xn.typ > ntStatic {\n\t\t\tif len(rctx.routeParams.Values) > 0 {\n\t\t\t\trctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values)-1]\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn nil\n}\n\nfunc (n *node) findEdge(ntyp nodeTyp, label byte) *node {\n\tnds := n.children[ntyp]\n\tnum := len(nds)\n\tidx := 0\n\n\tswitch ntyp {\n\tcase ntStatic, ntParam, ntRegexp:\n\t\ti, j := 0, num-1\n\t\tfor i <= j {\n\t\t\tidx = i + (j-i)/2\n\t\t\tif label > nds[idx].label {\n\t\t\t\ti = idx + 1\n\t\t\t} else if label < nds[idx].label {\n\t\t\t\tj = idx - 1\n\t\t\t} else {\n\t\t\t\ti = num // breaks cond\n\t\t\t}\n\t\t}\n\t\tif nds[idx].label != label {\n\t\t\treturn nil\n\t\t}\n\t\treturn nds[idx]\n\n\tdefault: // catch all\n\t\treturn nds[idx]\n\t}\n}\n\nfunc (n *node) isLeaf() bool {\n\treturn n.endpoints != nil\n}\n\nfunc (n *node) findPattern(pattern string) bool {\n\tnn := n\n\tfor _, nds := range nn.children {\n\t\tif len(nds) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tn = nn.findEdge(nds[0].typ, pattern[0])\n\t\tif n == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar idx int\n\t\tvar xpattern string\n\n\t\tswitch n.typ {\n\t\tcase ntStatic:\n\t\t\tidx = longestPrefix(pattern, n.prefix)\n\t\t\tif idx < len(n.prefix) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\tcase ntParam, ntRegexp:\n\t\t\tidx = strings.IndexByte(pattern, '}') + 1\n\n\t\tcase ntCatchAll:\n\t\t\tidx = longestPrefix(pattern, \"*\")\n\n\t\tdefault:\n\t\t\tpanic(\"chi: unknown node type\")\n\t\t}\n\n\t\txpattern = pattern[idx:]\n\t\tif len(xpattern) == 0 {\n\t\t\treturn true\n\t\t}\n\n\t\treturn n.findPattern(xpattern)\n\t}\n\treturn false\n}\n\nfunc (n *node) routes() []Route {\n\trts := []Route{}\n\n\tn.walk(func(eps endpoints, subroutes Routes) bool {\n\t\tif eps[mSTUB] != nil && eps[mSTUB].handler != nil && subroutes == nil {\n\t\t\treturn false\n\t\t}\n\n\t\t// Group methodHandlers by unique patterns\n\t\tpats := make(map[string]endpoints)\n\n\t\tfor mt, h := range eps {\n\t\t\tif h.pattern == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tp, ok := pats[h.pattern]\n\t\t\tif !ok {\n\t\t\t\tp = endpoints{}\n\t\t\t\tpats[h.pattern] = p\n\t\t\t}\n\t\t\tp[mt] = h\n\t\t}\n\n\t\tfor p, mh := range pats {\n\t\t\ths := make(map[string]http.Handler)\n\t\t\tif mh[mALL] != nil && mh[mALL].handler != nil {\n\t\t\t\ths[\"*\"] = mh[mALL].handler\n\t\t\t}\n\n\t\t\tfor mt, h := range mh {\n\t\t\t\tif h.handler == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif m, ok := reverseMethodMap[mt]; ok {\n\t\t\t\t\ths[m] = h.handler\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trt := Route{subroutes, hs, p}\n\t\t\trts = append(rts, rt)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn rts\n}\n\nfunc (n *node) walk(fn func(eps endpoints, subroutes Routes) bool) bool {\n\t// Visit the leaf values if any\n\tif (n.endpoints != nil || n.subroutes != nil) && fn(n.endpoints, n.subroutes) {\n\t\treturn true\n\t}\n\n\t// Recurse on the children\n\tfor _, ns := range n.children {\n\t\tfor _, cn := range ns {\n\t\t\tif cn.walk(fn) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// patNextSegment returns the next segment details from a pattern:\n// node type, param key, regexp string, param tail byte, param starting index, param ending index\nfunc patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) {\n\tps := strings.Index(pattern, \"{\")\n\tws := strings.Index(pattern, \"*\")\n\n\tif ps < 0 && ws < 0 {\n\t\treturn ntStatic, \"\", \"\", 0, 0, len(pattern) // we return the entire thing\n\t}\n\n\t// Sanity check\n\tif ps >= 0 && ws >= 0 && ws < ps {\n\t\tpanic(\"chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'\")\n\t}\n\n\tvar tail byte = '/' // Default endpoint tail to / byte\n\n\tif ps >= 0 {\n\t\t// Param/Regexp pattern is next\n\t\tnt := ntParam\n\n\t\t// Read to closing } taking into account opens and closes in curl count (cc)\n\t\tcc := 0\n\t\tpe := ps\n\t\tfor i, c := range pattern[ps:] {\n\t\t\tif c == '{' {\n\t\t\t\tcc++\n\t\t\t} else if c == '}' {\n\t\t\t\tcc--\n\t\t\t\tif cc == 0 {\n\t\t\t\t\tpe = ps + i\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif pe == ps {\n\t\t\tpanic(\"chi: route param closing delimiter '}' is missing\")\n\t\t}\n\n\t\tkey := pattern[ps+1 : pe]\n\t\tpe++ // set end to next position\n\n\t\tif pe < len(pattern) {\n\t\t\ttail = pattern[pe]\n\t\t}\n\n\t\tkey, rexpat, isRegexp := strings.Cut(key, \":\")\n\t\tif isRegexp {\n\t\t\tnt = ntRegexp\n\t\t}\n\n\t\tif len(rexpat) > 0 {\n\t\t\tif rexpat[0] != '^' {\n\t\t\t\trexpat = \"^\" + rexpat\n\t\t\t}\n\t\t\tif rexpat[len(rexpat)-1] != '$' {\n\t\t\t\trexpat += \"$\"\n\t\t\t}\n\t\t}\n\n\t\treturn nt, key, rexpat, tail, ps, pe\n\t}\n\n\t// Wildcard pattern as finale\n\tif ws < len(pattern)-1 {\n\t\tpanic(\"chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead\")\n\t}\n\treturn ntCatchAll, \"*\", \"\", 0, ws, len(pattern)\n}\n\nfunc patParamKeys(pattern string) []string {\n\tpat := pattern\n\tparamKeys := []string{}\n\tfor {\n\t\tptyp, paramKey, _, _, _, e := patNextSegment(pat)\n\t\tif ptyp == ntStatic {\n\t\t\treturn paramKeys\n\t\t}\n\t\tfor i := 0; i < len(paramKeys); i++ {\n\t\t\tif paramKeys[i] == paramKey {\n\t\t\t\tpanic(fmt.Sprintf(\"chi: routing pattern '%s' contains duplicate param key, '%s'\", pattern, paramKey))\n\t\t\t}\n\t\t}\n\t\tparamKeys = append(paramKeys, paramKey)\n\t\tpat = pat[e:]\n\t}\n}\n\n// longestPrefix finds the length of the shared prefix of two strings\nfunc longestPrefix(k1, k2 string) (i int) {\n\tfor i = 0; i < min(len(k1), len(k2)); i++ {\n\t\tif k1[i] != k2[i] {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn\n}\n\ntype nodes []*node\n\n// Sort the list of nodes by label\nfunc (ns nodes) Sort()              { sort.Sort(ns); ns.tailSort() }\nfunc (ns nodes) Len() int           { return len(ns) }\nfunc (ns nodes) Swap(i, j int)      { ns[i], ns[j] = ns[j], ns[i] }\nfunc (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label }\n\n// tailSort pushes nodes with '/' as the tail to the end of the list for param nodes.\n// The list order determines the traversal order.\nfunc (ns nodes) tailSort() {\n\tfor i := len(ns) - 1; i >= 0; i-- {\n\t\tif ns[i].typ > ntStatic && ns[i].tail == '/' {\n\t\t\tns.Swap(i, len(ns)-1)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (ns nodes) findEdge(label byte) *node {\n\tnum := len(ns)\n\tidx := 0\n\ti, j := 0, num-1\n\tfor i <= j {\n\t\tidx = i + (j-i)/2\n\t\tif label > ns[idx].label {\n\t\t\ti = idx + 1\n\t\t} else if label < ns[idx].label {\n\t\t\tj = idx - 1\n\t\t} else {\n\t\t\ti = num // breaks cond\n\t\t}\n\t}\n\tif ns[idx].label != label {\n\t\treturn nil\n\t}\n\treturn ns[idx]\n}\n\n// Route describes the details of a routing handler.\n// Handlers map key is an HTTP method\ntype Route struct {\n\tSubRoutes Routes\n\tHandlers  map[string]http.Handler\n\tPattern   string\n}\n\n// WalkFunc is the type of the function called for each method and route visited by Walk.\ntype WalkFunc func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error\n\n// Walk walks any router tree that implements Routes interface.\nfunc Walk(r Routes, walkFn WalkFunc) error {\n\treturn walk(r, walkFn, \"\")\n}\n\nfunc walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.Handler) http.Handler) error {\n\tfor _, route := range r.Routes() {\n\t\tmws := slices.Concat(parentMw, r.Middlewares())\n\n\t\tif route.SubRoutes != nil {\n\t\t\tif handler, ok := route.Handlers[\"*\"]; ok {\n\t\t\t\tif chain, ok := handler.(*ChainHandler); ok {\n\t\t\t\t\tmws = append(mws, chain.Middlewares...)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err := walk(route.SubRoutes, walkFn, parentRoute+route.Pattern, mws...); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tfor method, handler := range route.Handlers {\n\t\t\tif method == \"*\" {\n\t\t\t\t// Ignore a \"catchAll\" method, since we pass down all the specific methods for each route.\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfullRoute := parentRoute + route.Pattern\n\t\t\tfullRoute = strings.ReplaceAll(fullRoute, \"/*/\", \"/\")\n\n\t\t\tif chain, ok := handler.(*ChainHandler); ok {\n\t\t\t\tif err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := walkFn(method, fullRoute, handler, mws...); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "tree_test.go",
    "content": "package chi\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestTree(t *testing.T) {\n\thStub := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thIndex := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thFavicon := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thArticleList := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thArticleNear := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thArticleShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thArticleShowRelated := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thArticleShowOpts := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thArticleSlug := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thArticleByUser := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thUserList := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thUserShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thAdminCatchall := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thAdminAppShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thAdminAppShowCatchall := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thUserProfile := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thUserSuper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thUserAll := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thHubView1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thHubView2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thHubView3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\n\ttr := &node{}\n\n\ttr.InsertRoute(mGET, \"/\", hIndex)\n\ttr.InsertRoute(mGET, \"/favicon.ico\", hFavicon)\n\n\ttr.InsertRoute(mGET, \"/pages/*\", hStub)\n\n\ttr.InsertRoute(mGET, \"/article\", hArticleList)\n\ttr.InsertRoute(mGET, \"/article/\", hArticleList)\n\n\ttr.InsertRoute(mGET, \"/article/near\", hArticleNear)\n\ttr.InsertRoute(mGET, \"/article/{id}\", hStub)\n\ttr.InsertRoute(mGET, \"/article/{id}\", hArticleShow)\n\ttr.InsertRoute(mGET, \"/article/{id}\", hArticleShow) // duplicate will have no effect\n\ttr.InsertRoute(mGET, \"/article/@{user}\", hArticleByUser)\n\n\ttr.InsertRoute(mGET, \"/article/{sup}/{opts}\", hArticleShowOpts)\n\ttr.InsertRoute(mGET, \"/article/{id}/{opts}\", hArticleShowOpts) // overwrite above route, latest wins\n\n\ttr.InsertRoute(mGET, \"/article/{iffd}/edit\", hStub)\n\ttr.InsertRoute(mGET, \"/article/{id}//related\", hArticleShowRelated)\n\ttr.InsertRoute(mGET, \"/article/slug/{month}/-/{day}/{year}\", hArticleSlug)\n\n\ttr.InsertRoute(mGET, \"/admin/user\", hUserList)\n\ttr.InsertRoute(mGET, \"/admin/user/\", hStub) // will get replaced by next route\n\ttr.InsertRoute(mGET, \"/admin/user/\", hUserList)\n\n\ttr.InsertRoute(mGET, \"/admin/user//{id}\", hUserShow)\n\ttr.InsertRoute(mGET, \"/admin/user/{id}\", hUserShow)\n\n\ttr.InsertRoute(mGET, \"/admin/apps/{id}\", hAdminAppShow)\n\ttr.InsertRoute(mGET, \"/admin/apps/{id}/*\", hAdminAppShowCatchall)\n\n\ttr.InsertRoute(mGET, \"/admin/*\", hStub) // catchall segment will get replaced by next route\n\ttr.InsertRoute(mGET, \"/admin/*\", hAdminCatchall)\n\n\ttr.InsertRoute(mGET, \"/users/{userID}/profile\", hUserProfile)\n\ttr.InsertRoute(mGET, \"/users/super/*\", hUserSuper)\n\ttr.InsertRoute(mGET, \"/users/*\", hUserAll)\n\n\ttr.InsertRoute(mGET, \"/hubs/{hubID}/view\", hHubView1)\n\ttr.InsertRoute(mGET, \"/hubs/{hubID}/view/*\", hHubView2)\n\tsr := NewRouter()\n\tsr.Get(\"/users\", hHubView3)\n\ttr.InsertRoute(mGET, \"/hubs/{hubID}/*\", sr)\n\ttr.InsertRoute(mGET, \"/hubs/{hubID}/users\", hHubView3)\n\n\ttests := []struct {\n\t\tr string       // input request path\n\t\th http.Handler // output matched handler\n\t\tk []string     // output param keys\n\t\tv []string     // output param values\n\t}{\n\t\t{r: \"/\", h: hIndex, k: []string{}, v: []string{}},\n\t\t{r: \"/favicon.ico\", h: hFavicon, k: []string{}, v: []string{}},\n\n\t\t{r: \"/pages\", h: nil, k: []string{}, v: []string{}},\n\t\t{r: \"/pages/\", h: hStub, k: []string{\"*\"}, v: []string{\"\"}},\n\t\t{r: \"/pages/yes\", h: hStub, k: []string{\"*\"}, v: []string{\"yes\"}},\n\n\t\t{r: \"/article\", h: hArticleList, k: []string{}, v: []string{}},\n\t\t{r: \"/article/\", h: hArticleList, k: []string{}, v: []string{}},\n\t\t{r: \"/article/near\", h: hArticleNear, k: []string{}, v: []string{}},\n\t\t{r: \"/article/neard\", h: hArticleShow, k: []string{\"id\"}, v: []string{\"neard\"}},\n\t\t{r: \"/article/123\", h: hArticleShow, k: []string{\"id\"}, v: []string{\"123\"}},\n\t\t{r: \"/article/123/456\", h: hArticleShowOpts, k: []string{\"id\", \"opts\"}, v: []string{\"123\", \"456\"}},\n\t\t{r: \"/article/@peter\", h: hArticleByUser, k: []string{\"user\"}, v: []string{\"peter\"}},\n\t\t{r: \"/article/22//related\", h: hArticleShowRelated, k: []string{\"id\"}, v: []string{\"22\"}},\n\t\t{r: \"/article/111/edit\", h: hStub, k: []string{\"iffd\"}, v: []string{\"111\"}},\n\t\t{r: \"/article/slug/sept/-/4/2015\", h: hArticleSlug, k: []string{\"month\", \"day\", \"year\"}, v: []string{\"sept\", \"4\", \"2015\"}},\n\t\t{r: \"/article/:id\", h: hArticleShow, k: []string{\"id\"}, v: []string{\":id\"}},\n\n\t\t{r: \"/admin/user\", h: hUserList, k: []string{}, v: []string{}},\n\t\t{r: \"/admin/user/\", h: hUserList, k: []string{}, v: []string{}},\n\t\t{r: \"/admin/user/1\", h: hUserShow, k: []string{\"id\"}, v: []string{\"1\"}},\n\t\t{r: \"/admin/user//1\", h: hUserShow, k: []string{\"id\"}, v: []string{\"1\"}},\n\t\t{r: \"/admin/hi\", h: hAdminCatchall, k: []string{\"*\"}, v: []string{\"hi\"}},\n\t\t{r: \"/admin/lots/of/:fun\", h: hAdminCatchall, k: []string{\"*\"}, v: []string{\"lots/of/:fun\"}},\n\t\t{r: \"/admin/apps/333\", h: hAdminAppShow, k: []string{\"id\"}, v: []string{\"333\"}},\n\t\t{r: \"/admin/apps/333/woot\", h: hAdminAppShowCatchall, k: []string{\"id\", \"*\"}, v: []string{\"333\", \"woot\"}},\n\n\t\t{r: \"/hubs/123/view\", h: hHubView1, k: []string{\"hubID\"}, v: []string{\"123\"}},\n\t\t{r: \"/hubs/123/view/index.html\", h: hHubView2, k: []string{\"hubID\", \"*\"}, v: []string{\"123\", \"index.html\"}},\n\t\t{r: \"/hubs/123/users\", h: hHubView3, k: []string{\"hubID\"}, v: []string{\"123\"}},\n\n\t\t{r: \"/users/123/profile\", h: hUserProfile, k: []string{\"userID\"}, v: []string{\"123\"}},\n\t\t{r: \"/users/super/123/okay/yes\", h: hUserSuper, k: []string{\"*\"}, v: []string{\"123/okay/yes\"}},\n\t\t{r: \"/users/123/okay/yes\", h: hUserAll, k: []string{\"*\"}, v: []string{\"123/okay/yes\"}},\n\t}\n\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\t// debugPrintTree(0, 0, tr, 0)\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\n\tfor i, tt := range tests {\n\t\trctx := NewRouteContext()\n\n\t\t_, handlers, _ := tr.FindRoute(rctx, mGET, tt.r)\n\n\t\tvar handler http.Handler\n\t\tif methodHandler, ok := handlers[mGET]; ok {\n\t\t\thandler = methodHandler.handler\n\t\t}\n\n\t\tparamKeys := rctx.routeParams.Keys\n\t\tparamValues := rctx.routeParams.Values\n\n\t\tif fmt.Sprintf(\"%v\", tt.h) != fmt.Sprintf(\"%v\", handler) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting handler:%v , got:%v\", i, tt.r, tt.h, handler)\n\t\t}\n\t\tif !stringSliceEqual(tt.k, paramKeys) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v\", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)\n\t\t}\n\t\tif !stringSliceEqual(tt.v, paramValues) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v\", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)\n\t\t}\n\t}\n}\n\nfunc TestTreeMoar(t *testing.T) {\n\thStub := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub4 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub5 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub6 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub7 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub8 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub9 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub10 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub11 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub12 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub13 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub14 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub15 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub16 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\n\t// TODO: panic if we see {id}{x} because we're missing a delimiter, its not possible.\n\t// also {:id}* is not possible.\n\n\ttr := &node{}\n\n\ttr.InsertRoute(mGET, \"/articlefun\", hStub5)\n\ttr.InsertRoute(mGET, \"/articles/{id}\", hStub)\n\ttr.InsertRoute(mDELETE, \"/articles/{slug}\", hStub8)\n\ttr.InsertRoute(mGET, \"/articles/search\", hStub1)\n\ttr.InsertRoute(mGET, \"/articles/{id}:delete\", hStub8)\n\ttr.InsertRoute(mGET, \"/articles/{iidd}!sup\", hStub4)\n\ttr.InsertRoute(mGET, \"/articles/{id}:{op}\", hStub3)\n\ttr.InsertRoute(mGET, \"/articles/{id}:{op}\", hStub2)                              // this route sets a new handler for the above route\n\ttr.InsertRoute(mGET, \"/articles/{slug:^[a-z]+}/posts\", hStub)                    // up to tail '/' will only match if contents match the rex\n\ttr.InsertRoute(mGET, \"/articles/{id}/posts/{pid}\", hStub6)                       // /articles/123/posts/1\n\ttr.InsertRoute(mGET, \"/articles/{id}/posts/{month}/{day}/{year}/{slug}\", hStub7) // /articles/123/posts/09/04/1984/juice\n\ttr.InsertRoute(mGET, \"/articles/{id}.json\", hStub10)\n\ttr.InsertRoute(mGET, \"/articles/{id}/data.json\", hStub11)\n\ttr.InsertRoute(mGET, \"/articles/files/{file}.{ext}\", hStub12)\n\ttr.InsertRoute(mPUT, \"/articles/me\", hStub13)\n\n\t// TODO: make a separate test case for this one..\n\t// tr.InsertRoute(mGET, \"/articles/{id}/{id}\", hStub1)                              // panic expected, we're duplicating param keys\n\n\ttr.InsertRoute(mGET, \"/pages/*\", hStub)\n\ttr.InsertRoute(mGET, \"/pages/*\", hStub9)\n\n\ttr.InsertRoute(mGET, \"/users/{id}\", hStub14)\n\ttr.InsertRoute(mGET, \"/users/{id}/settings/{key}\", hStub15)\n\ttr.InsertRoute(mGET, \"/users/{id}/settings/*\", hStub16)\n\n\ttests := []struct {\n\t\th http.Handler\n\t\tr string\n\t\tk []string\n\t\tv []string\n\t\tm methodTyp\n\t}{\n\t\t{m: mGET, r: \"/articles/search\", h: hStub1, k: []string{}, v: []string{}},\n\t\t{m: mGET, r: \"/articlefun\", h: hStub5, k: []string{}, v: []string{}},\n\t\t{m: mGET, r: \"/articles/123\", h: hStub, k: []string{\"id\"}, v: []string{\"123\"}},\n\t\t{m: mDELETE, r: \"/articles/123mm\", h: hStub8, k: []string{\"slug\"}, v: []string{\"123mm\"}},\n\t\t{m: mGET, r: \"/articles/789:delete\", h: hStub8, k: []string{\"id\"}, v: []string{\"789\"}},\n\t\t{m: mGET, r: \"/articles/789!sup\", h: hStub4, k: []string{\"iidd\"}, v: []string{\"789\"}},\n\t\t{m: mGET, r: \"/articles/123:sync\", h: hStub2, k: []string{\"id\", \"op\"}, v: []string{\"123\", \"sync\"}},\n\t\t{m: mGET, r: \"/articles/456/posts/1\", h: hStub6, k: []string{\"id\", \"pid\"}, v: []string{\"456\", \"1\"}},\n\t\t{m: mGET, r: \"/articles/456/posts/09/04/1984/juice\", h: hStub7, k: []string{\"id\", \"month\", \"day\", \"year\", \"slug\"}, v: []string{\"456\", \"09\", \"04\", \"1984\", \"juice\"}},\n\t\t{m: mGET, r: \"/articles/456.json\", h: hStub10, k: []string{\"id\"}, v: []string{\"456\"}},\n\t\t{m: mGET, r: \"/articles/456/data.json\", h: hStub11, k: []string{\"id\"}, v: []string{\"456\"}},\n\n\t\t{m: mGET, r: \"/articles/files/file.zip\", h: hStub12, k: []string{\"file\", \"ext\"}, v: []string{\"file\", \"zip\"}},\n\t\t{m: mGET, r: \"/articles/files/photos.tar.gz\", h: hStub12, k: []string{\"file\", \"ext\"}, v: []string{\"photos\", \"tar.gz\"}},\n\t\t{m: mGET, r: \"/articles/files/photos.tar.gz\", h: hStub12, k: []string{\"file\", \"ext\"}, v: []string{\"photos\", \"tar.gz\"}},\n\n\t\t{m: mPUT, r: \"/articles/me\", h: hStub13, k: []string{}, v: []string{}},\n\t\t{m: mGET, r: \"/articles/me\", h: hStub, k: []string{\"id\"}, v: []string{\"me\"}},\n\t\t{m: mGET, r: \"/pages\", h: nil, k: []string{}, v: []string{}},\n\t\t{m: mGET, r: \"/pages/\", h: hStub9, k: []string{\"*\"}, v: []string{\"\"}},\n\t\t{m: mGET, r: \"/pages/yes\", h: hStub9, k: []string{\"*\"}, v: []string{\"yes\"}},\n\n\t\t{m: mGET, r: \"/users/1\", h: hStub14, k: []string{\"id\"}, v: []string{\"1\"}},\n\t\t{m: mGET, r: \"/users/\", h: nil, k: []string{}, v: []string{}},\n\t\t{m: mGET, r: \"/users/2/settings/password\", h: hStub15, k: []string{\"id\", \"key\"}, v: []string{\"2\", \"password\"}},\n\t\t{m: mGET, r: \"/users/2/settings/\", h: hStub16, k: []string{\"id\", \"*\"}, v: []string{\"2\", \"\"}},\n\t}\n\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\t// debugPrintTree(0, 0, tr, 0)\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\n\tfor i, tt := range tests {\n\t\trctx := NewRouteContext()\n\n\t\t_, handlers, _ := tr.FindRoute(rctx, tt.m, tt.r)\n\n\t\tvar handler http.Handler\n\t\tif methodHandler, ok := handlers[tt.m]; ok {\n\t\t\thandler = methodHandler.handler\n\t\t}\n\n\t\tparamKeys := rctx.routeParams.Keys\n\t\tparamValues := rctx.routeParams.Values\n\n\t\tif fmt.Sprintf(\"%v\", tt.h) != fmt.Sprintf(\"%v\", handler) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting handler:%v , got:%v\", i, tt.r, tt.h, handler)\n\t\t}\n\t\tif !stringSliceEqual(tt.k, paramKeys) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v\", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)\n\t\t}\n\t\tif !stringSliceEqual(tt.v, paramValues) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v\", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)\n\t\t}\n\t}\n}\n\nfunc TestTreeRegexp(t *testing.T) {\n\thStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub4 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub5 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub6 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub7 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\n\ttr := &node{}\n\ttr.InsertRoute(mGET, \"/articles/{rid:^[0-9]{5,6}}\", hStub7)\n\ttr.InsertRoute(mGET, \"/articles/{zid:^0[0-9]+}\", hStub3)\n\ttr.InsertRoute(mGET, \"/articles/{name:^@[a-z]+}/posts\", hStub4)\n\ttr.InsertRoute(mGET, \"/articles/{op:^[0-9]+}/run\", hStub5)\n\ttr.InsertRoute(mGET, \"/articles/{id:^[0-9]+}\", hStub1)\n\ttr.InsertRoute(mGET, \"/articles/{id:^[1-9]+}-{aux}\", hStub6)\n\ttr.InsertRoute(mGET, \"/articles/{slug}\", hStub2)\n\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\t// debugPrintTree(0, 0, tr, 0)\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\n\ttests := []struct {\n\t\tr string       // input request path\n\t\th http.Handler // output matched handler\n\t\tk []string     // output param keys\n\t\tv []string     // output param values\n\t}{\n\t\t{r: \"/articles\", h: nil, k: []string{}, v: []string{}},\n\t\t{r: \"/articles/12345\", h: hStub7, k: []string{\"rid\"}, v: []string{\"12345\"}},\n\t\t{r: \"/articles/123\", h: hStub1, k: []string{\"id\"}, v: []string{\"123\"}},\n\t\t{r: \"/articles/how-to-build-a-router\", h: hStub2, k: []string{\"slug\"}, v: []string{\"how-to-build-a-router\"}},\n\t\t{r: \"/articles/0456\", h: hStub3, k: []string{\"zid\"}, v: []string{\"0456\"}},\n\t\t{r: \"/articles/@pk/posts\", h: hStub4, k: []string{\"name\"}, v: []string{\"@pk\"}},\n\t\t{r: \"/articles/1/run\", h: hStub5, k: []string{\"op\"}, v: []string{\"1\"}},\n\t\t{r: \"/articles/1122\", h: hStub1, k: []string{\"id\"}, v: []string{\"1122\"}},\n\t\t{r: \"/articles/1122-yes\", h: hStub6, k: []string{\"id\", \"aux\"}, v: []string{\"1122\", \"yes\"}},\n\t}\n\n\tfor i, tt := range tests {\n\t\trctx := NewRouteContext()\n\n\t\t_, handlers, _ := tr.FindRoute(rctx, mGET, tt.r)\n\n\t\tvar handler http.Handler\n\t\tif methodHandler, ok := handlers[mGET]; ok {\n\t\t\thandler = methodHandler.handler\n\t\t}\n\n\t\tparamKeys := rctx.routeParams.Keys\n\t\tparamValues := rctx.routeParams.Values\n\n\t\tif fmt.Sprintf(\"%v\", tt.h) != fmt.Sprintf(\"%v\", handler) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting handler:%v , got:%v\", i, tt.r, tt.h, handler)\n\t\t}\n\t\tif !stringSliceEqual(tt.k, paramKeys) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v\", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)\n\t\t}\n\t\tif !stringSliceEqual(tt.v, paramValues) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v\", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)\n\t\t}\n\t}\n}\n\nfunc TestTreeRegexpRecursive(t *testing.T) {\n\thStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\n\ttr := &node{}\n\ttr.InsertRoute(mGET, \"/one/{firstId:[a-z0-9-]+}/{secondId:[a-z0-9-]+}/first\", hStub1)\n\ttr.InsertRoute(mGET, \"/one/{firstId:[a-z0-9-_]+}/{secondId:[a-z0-9-_]+}/second\", hStub2)\n\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\t// debugPrintTree(0, 0, tr, 0)\n\t// log.Println(\"~~~~~~~~~\")\n\t// log.Println(\"~~~~~~~~~\")\n\n\ttests := []struct {\n\t\tr string       // input request path\n\t\th http.Handler // output matched handler\n\t\tk []string     // output param keys\n\t\tv []string     // output param values\n\t}{\n\t\t{r: \"/one/hello/world/first\", h: hStub1, k: []string{\"firstId\", \"secondId\"}, v: []string{\"hello\", \"world\"}},\n\t\t{r: \"/one/hi_there/ok/second\", h: hStub2, k: []string{\"firstId\", \"secondId\"}, v: []string{\"hi_there\", \"ok\"}},\n\t\t{r: \"/one///first\", h: nil, k: []string{}, v: []string{}},\n\t\t{r: \"/one/hi/123/second\", h: hStub2, k: []string{\"firstId\", \"secondId\"}, v: []string{\"hi\", \"123\"}},\n\t}\n\n\tfor i, tt := range tests {\n\t\trctx := NewRouteContext()\n\n\t\t_, handlers, _ := tr.FindRoute(rctx, mGET, tt.r)\n\n\t\tvar handler http.Handler\n\t\tif methodHandler, ok := handlers[mGET]; ok {\n\t\t\thandler = methodHandler.handler\n\t\t}\n\n\t\tparamKeys := rctx.routeParams.Keys\n\t\tparamValues := rctx.routeParams.Values\n\n\t\tif fmt.Sprintf(\"%v\", tt.h) != fmt.Sprintf(\"%v\", handler) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting handler:%v , got:%v\", i, tt.r, tt.h, handler)\n\t\t}\n\t\tif !stringSliceEqual(tt.k, paramKeys) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v\", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)\n\t\t}\n\t\tif !stringSliceEqual(tt.v, paramValues) {\n\t\t\tt.Errorf(\"input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v\", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)\n\t\t}\n\t}\n}\n\nfunc TestTreeRegexMatchWholeParam(t *testing.T) {\n\thStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\n\trctx := NewRouteContext()\n\ttr := &node{}\n\ttr.InsertRoute(mGET, \"/{id:[0-9]+}\", hStub1)\n\ttr.InsertRoute(mGET, \"/{x:.+}/foo\", hStub1)\n\ttr.InsertRoute(mGET, \"/{param:[0-9]*}/test\", hStub1)\n\n\ttests := []struct {\n\t\texpectedHandler http.Handler\n\t\turl             string\n\t}{\n\t\t{url: \"/13\", expectedHandler: hStub1},\n\t\t{url: \"/a13\", expectedHandler: nil},\n\t\t{url: \"/13.jpg\", expectedHandler: nil},\n\t\t{url: \"/a13.jpg\", expectedHandler: nil},\n\t\t{url: \"/a/foo\", expectedHandler: hStub1},\n\t\t{url: \"//foo\", expectedHandler: nil},\n\t\t{url: \"//test\", expectedHandler: hStub1},\n\t}\n\n\tfor _, tc := range tests {\n\t\t_, _, handler := tr.FindRoute(rctx, mGET, tc.url)\n\t\tif fmt.Sprintf(\"%v\", tc.expectedHandler) != fmt.Sprintf(\"%v\", handler) {\n\t\t\tt.Errorf(\"url %v: expecting handler:%v , got:%v\", tc.url, tc.expectedHandler, handler)\n\t\t}\n\t}\n}\n\nfunc TestTreeFindPattern(t *testing.T) {\n\thStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\thStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\n\ttr := &node{}\n\ttr.InsertRoute(mGET, \"/pages/*\", hStub1)\n\ttr.InsertRoute(mGET, \"/articles/{id}/*\", hStub2)\n\ttr.InsertRoute(mGET, \"/articles/{slug}/{uid}/*\", hStub3)\n\n\tif tr.findPattern(\"/pages\") != false {\n\t\tt.Errorf(\"find /pages failed\")\n\t}\n\tif tr.findPattern(\"/pages*\") != false {\n\t\tt.Errorf(\"find /pages* failed - should be nil\")\n\t}\n\tif tr.findPattern(\"/pages/*\") == false {\n\t\tt.Errorf(\"find /pages/* failed\")\n\t}\n\tif tr.findPattern(\"/articles/{id}/*\") == false {\n\t\tt.Errorf(\"find /articles/{id}/* failed\")\n\t}\n\tif tr.findPattern(\"/articles/{something}/*\") == false {\n\t\tt.Errorf(\"find /articles/{something}/* failed\")\n\t}\n\tif tr.findPattern(\"/articles/{slug}/{uid}/*\") == false {\n\t\tt.Errorf(\"find /articles/{slug}/{uid}/* failed\")\n\t}\n}\n\nfunc debugPrintTree(parent int, i int, n *node, label byte) bool {\n\tnumEdges := 0\n\tfor _, nds := range n.children {\n\t\tnumEdges += len(nds)\n\t}\n\n\t// if n.handlers != nil {\n\t// \tlog.Printf(\"[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v handler:%v pat:%s keys:%v\\n\", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.handlers, n.pattern, n.paramKeys)\n\t// } else {\n\t// \tlog.Printf(\"[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v pat:%s keys:%v\\n\", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.pattern, n.paramKeys)\n\t// }\n\tif n.endpoints != nil {\n\t\tlog.Printf(\"[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v handler:%v\\n\", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.endpoints)\n\t} else {\n\t\tlog.Printf(\"[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v\\n\", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf())\n\t}\n\tparent = i\n\tfor _, nds := range n.children {\n\t\tfor _, e := range nds {\n\t\t\ti++\n\t\t\tif debugPrintTree(parent, i, e, e.label) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc stringSliceEqual(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif b[i] != a[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc BenchmarkTreeGet(b *testing.B) {\n\th1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\th2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\n\n\ttr := &node{}\n\ttr.InsertRoute(mGET, \"/\", h1)\n\ttr.InsertRoute(mGET, \"/ping\", h2)\n\ttr.InsertRoute(mGET, \"/pingall\", h2)\n\ttr.InsertRoute(mGET, \"/ping/{id}\", h2)\n\ttr.InsertRoute(mGET, \"/ping/{id}/woop\", h2)\n\ttr.InsertRoute(mGET, \"/ping/{id}/{opt}\", h2)\n\ttr.InsertRoute(mGET, \"/pinggggg\", h2)\n\ttr.InsertRoute(mGET, \"/hello\", h1)\n\n\tmctx := NewRouteContext()\n\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tmctx.Reset()\n\t\ttr.FindRoute(mctx, mGET, \"/ping/123/456\")\n\t}\n}\n\nfunc TestWalker(t *testing.T) {\n\tr := bigMux()\n\n\t// Walk the muxBig router tree.\n\tif err := Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {\n\t\tt.Logf(\"%v %v\", method, route)\n\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestWalkInlineMiddlewaresAcrossSubrouter(t *testing.T) {\n\tmw := func(next http.Handler) http.Handler { return next }\n\thandler := func(w http.ResponseWriter, r *http.Request) {}\n\n\ttests := []struct {\n\t\tname     string\n\t\tsetup    func() Router\n\t\texpected int\n\t}{\n\t\t{\n\t\t\tname: \"With+Group wrapping Route+With\",\n\t\t\tsetup: func() Router {\n\t\t\t\tr := NewMux()\n\t\t\t\tr.With(mw).Group(func(r Router) {\n\t\t\t\t\tr.Route(\"/foo\", func(r Router) {\n\t\t\t\t\t\tr.With(mw).Post(\"/bar\", handler)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\treturn r\n\t\t\t},\n\t\t\texpected: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"Use on subrouter combined with outer With\",\n\t\t\tsetup: func() Router {\n\t\t\t\tr := NewMux()\n\t\t\t\tr.With(mw).Group(func(r Router) {\n\t\t\t\t\tr.Route(\"/foo\", func(r Router) {\n\t\t\t\t\t\tr.Use(mw)\n\t\t\t\t\t\tr.With(mw).Post(\"/bar\", handler)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\treturn r\n\t\t\t},\n\t\t\texpected: 3,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr := tt.setup()\n\t\t\tvar middlewareCount int\n\t\t\terr := Walk(r, func(method, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {\n\t\t\t\tmiddlewareCount = len(middlewares)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif middlewareCount != tt.expected {\n\t\t\t\tt.Fatalf(\"expected %d middlewares, got %d\", tt.expected, middlewareCount)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  }
]