Showing preview only (1,208K chars total). Download the full file or copy to clipboard to get everything.
Repository: labstack/echo
Branch: master
Commit: 675712da34c1
Files: 127
Total size: 1.1 MB
Directory structure:
gitextract_q3zebxle/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE.md
│ ├── stale.yml
│ └── workflows/
│ ├── checks.yml
│ └── echo.yml
├── .gitignore
├── API_CHANGES_V5.md
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── _fixture/
│ ├── _fixture/
│ │ └── README.md
│ ├── certs/
│ │ ├── README.md
│ │ ├── cert.pem
│ │ └── key.pem
│ ├── dist/
│ │ ├── private.txt
│ │ └── public/
│ │ ├── assets/
│ │ │ ├── readme.md
│ │ │ └── subfolder/
│ │ │ └── subfolder.md
│ │ ├── index.html
│ │ └── test.txt
│ ├── folder/
│ │ └── index.html
│ └── index.html
├── bind.go
├── bind_test.go
├── binder.go
├── binder_external_test.go
├── binder_generic.go
├── binder_generic_test.go
├── binder_test.go
├── codecov.yml
├── context.go
├── context_generic.go
├── context_generic_test.go
├── context_test.go
├── echo.go
├── echo_test.go
├── echotest/
│ ├── context.go
│ ├── context_external_test.go
│ ├── context_test.go
│ ├── reader.go
│ ├── reader_external_test.go
│ ├── reader_test.go
│ └── testdata/
│ └── test.json
├── go.mod
├── go.sum
├── group.go
├── group_test.go
├── httperror.go
├── httperror_external_test.go
├── httperror_test.go
├── ip.go
├── ip_test.go
├── json.go
├── json_test.go
├── middleware/
│ ├── DEVELOPMENT.md
│ ├── basic_auth.go
│ ├── basic_auth_test.go
│ ├── body_dump.go
│ ├── body_dump_test.go
│ ├── body_limit.go
│ ├── body_limit_test.go
│ ├── compress.go
│ ├── compress_test.go
│ ├── context_timeout.go
│ ├── context_timeout_test.go
│ ├── cors.go
│ ├── cors_test.go
│ ├── csrf.go
│ ├── csrf_test.go
│ ├── decompress.go
│ ├── decompress_test.go
│ ├── extractor.go
│ ├── extractor_test.go
│ ├── key_auth.go
│ ├── key_auth_test.go
│ ├── method_override.go
│ ├── method_override_test.go
│ ├── middleware.go
│ ├── middleware_test.go
│ ├── proxy.go
│ ├── proxy_test.go
│ ├── rate_limiter.go
│ ├── rate_limiter_test.go
│ ├── recover.go
│ ├── recover_test.go
│ ├── redirect.go
│ ├── redirect_test.go
│ ├── request_id.go
│ ├── request_id_test.go
│ ├── request_logger.go
│ ├── request_logger_test.go
│ ├── rewrite.go
│ ├── rewrite_test.go
│ ├── secure.go
│ ├── secure_test.go
│ ├── slash.go
│ ├── slash_test.go
│ ├── static.go
│ ├── static_other.go
│ ├── static_test.go
│ ├── testdata/
│ │ ├── dist/
│ │ │ ├── private.txt
│ │ │ └── public/
│ │ │ ├── assets/
│ │ │ │ ├── readme.md
│ │ │ │ └── subfolder/
│ │ │ │ └── subfolder.md
│ │ │ ├── index.html
│ │ │ └── test.txt
│ │ └── private.txt
│ ├── util.go
│ └── util_test.go
├── renderer.go
├── renderer_test.go
├── response.go
├── response_test.go
├── route.go
├── route_test.go
├── router.go
├── router_concurrent.go
├── router_concurrent_test.go
├── router_test.go
├── server.go
├── server_test.go
├── version.go
├── vhost.go
└── vhost_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# EditorConfig coding styles definitions. For more information about the
# properties used in this file, please see the EditorConfig documentation:
# http://editorconfig.org/
# indicate this is the root of the project
root = true
[*]
charset = utf-8
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
[*.go]
indent_style = tab
================================================
FILE: .gitattributes
================================================
# Automatically normalize line endings for all text-based files
# http://git-scm.com/docs/gitattributes#_end_of_line_conversion
* text=auto
# For the following file types, normalize line endings to LF on checking and
# prevent conversion to CRLF when they are checked out (this is required in
# order to prevent newline related issues)
.* text eol=lf
*.go text eol=lf
*.yml text eol=lf
*.html text eol=lf
*.css text eol=lf
*.js text eol=lf
*.json text eol=lf
LICENSE text eol=lf
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [labstack]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
### Issue Description
### Working code to debug
```go
package main
import (
"github.com/labstack/echo/v5"
"net/http"
"net/http/httptest"
"testing"
)
func TestExample(t *testing.T) {
e := echo.New()
e.GET("/", func(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("got %d, want %d", rec.Code, http.StatusOK)
}
}
```
### Version/commit
================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 30
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- bug
- enhancement
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed within a month if no further activity occurs.
Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
================================================
FILE: .github/workflows/checks.yml
================================================
name: Run checks
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
permissions:
contents: read # to fetch code (actions/checkout)
env:
# run static analysis only with the latest Go version
LATEST_GO_VERSION: "1.26"
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v5
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v5
with:
go-version: ${{ env.LATEST_GO_VERSION }}
check-latest: true
- name: Run golint
run: |
go install golang.org/x/lint/golint@latest
golint -set_exit_status ./...
- name: Run staticcheck
run: |
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
- name: Run govulncheck
run: |
go version
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
================================================
FILE: .github/workflows/echo.yml
================================================
name: Run Tests
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
permissions:
contents: read # to fetch code (actions/checkout)
env:
# run coverage and benchmarks only with the latest Go version
LATEST_GO_VERSION: "1.26"
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# Each major Go release is supported until there are two newer major releases. https://golang.org/doc/devel/release.html#policy
# Echo tests with last four major releases (unless there are pressing vulnerabilities)
# As we depend on `golang.org/x/` libraries which only support the last 2 Go releases, we could have situations when
# we derive from the last four major releases promise.
go: ["1.25", "1.26"]
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Code
uses: actions/checkout@v5
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Run Tests
run: go test -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
- name: Upload coverage to Codecov
if: success() && matrix.go == env.LATEST_GO_VERSION && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v5
with:
token:
fail_ci_if_error: false
benchmark:
needs: test
name: Benchmark comparison
runs-on: ubuntu-latest
steps:
- name: Checkout Code (Previous)
uses: actions/checkout@v5
with:
ref: ${{ github.base_ref }}
path: previous
- name: Checkout Code (New)
uses: actions/checkout@v5
with:
path: new
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v5
with:
go-version: ${{ env.LATEST_GO_VERSION }}
- name: Install Dependencies
run: go install golang.org/x/perf/cmd/benchstat@latest
- name: Run Benchmark (Previous)
run: |
cd previous
go test -run="-" -bench=".*" -count=8 ./... > benchmark.txt
- name: Run Benchmark (New)
run: |
cd new
go test -run="-" -bench=".*" -count=8 ./... > benchmark.txt
- name: Run Benchstat
run: |
benchstat previous/benchmark.txt new/benchmark.txt
================================================
FILE: .gitignore
================================================
.DS_Store
coverage.txt
_test
vendor
.idea
*.iml
*.out
.vscode
================================================
FILE: API_CHANGES_V5.md
================================================
# Echo v5 Public API Changes
**Comparison between `master` (v4.15.0) and `v5` (v5.0.0-alpha) branches**
Generated: 2026-01-01
---
## Executive Summary (by authors)
Echo `v5` is maintenance release with **major breaking changes**
- `Context` is now struct instead of interface and we can add method to it in the future in minor versions.
- Adds new `Router` interface for possible new routing implementations.
- Drops old logging interface and uses moderm `log/slog` instead.
- Rearranges alot of methods/function signatures to make them more consistent.
## Executive Summary (by LLMs)
Echo v5 represents a **major breaking release** with significant architectural changes focused on:
- **Updated generic helpers** to take `*Context` and rename form helpers to `FormValue*`
- **Simplified API surface** by moving Context from interface to concrete struct
- **Modern Go patterns** including slog.Logger integration
- **Enhanced routing** with explicit RouteInfo and Routes types
- **Better error handling** with simplified HTTPError
- **New test helpers** via the `echotest` package
### Change Statistics
- **Major Breaking Changes**: 15+
- **New Functions Added**: 30+
- **Type Signature Changes**: 20+
- **Removed APIs**: 10+
- **New Packages Added**: 1 (`echotest`)
- **Version Change**: `4.15.0` → `5.0.0-alpha`
---
## Critical Breaking Changes
### 1. **Context: Interface → Concrete Struct**
**v4 (master):**
```go
type Context interface {
Request() *http.Request
// ... many methods
}
// Handler signature
func handler(c echo.Context) error
```
**v5:**
```go
type Context struct {
// Has unexported fields
}
// Handler signature - NOW USES POINTER!
func handler(c *echo.Context) error
```
**Impact:** 🔴 **CRITICAL BREAKING CHANGE**
- ALL handlers must change from `echo.Context` to `*echo.Context`
- Context is now a concrete struct, not an interface
- This affects every single handler function in user code
**Migration:**
```go
// Before (v4)
func MyHandler(c echo.Context) error {
return c.JSON(200, map[string]string{"hello": "world"})
}
// After (v5)
func MyHandler(c *echo.Context) error {
return c.JSON(200, map[string]string{"hello": "world"})
}
```
---
### 2. **Logger: Custom Interface → slog.Logger**
**v4:**
```go
type Echo struct {
Logger Logger // Custom interface with Print, Debug, Info, etc.
}
type Logger interface {
Output() io.Writer
SetOutput(w io.Writer)
Prefix() string
// ... many custom methods
}
// Context returns Logger interface
func (c Context) Logger() Logger
```
**v5:**
```go
type Echo struct {
Logger *slog.Logger // Standard library structured logger
}
// Context returns slog.Logger
func (c *Context) Logger() *slog.Logger
func (c *Context) SetLogger(logger *slog.Logger)
```
**Impact:** 🔴 **BREAKING CHANGE**
- Must use Go's standard `log/slog` package
- Logger interface completely removed
- All logging code needs updating
---
### 3. **Router: From Router to DefaultRouter**
**v4:**
```go
type Router struct { ... }
func NewRouter(e *Echo) *Router
func (e *Echo) Router() *Router
```
**v5:**
```go
type DefaultRouter struct { ... }
func NewRouter(config RouterConfig) *DefaultRouter
func (e *Echo) Router() Router // Returns interface
```
**Changes:**
- New `Router` interface introduced
- `DefaultRouter` is the concrete implementation
- `NewRouter()` now takes `RouterConfig` instead of `*Echo`
- Added `NewConcurrentRouter(r Router) Router` for thread-safe routing
---
### 4. **Route Return Types Changed**
**v4:**
```go
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route
func (e *Echo) Any(path string, h HandlerFunc, m ...MiddlewareFunc) []*Route
func (e *Echo) Routes() []*Route
```
**v5:**
```go
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo
func (e *Echo) Any(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo
func (e *Echo) Match(...) Routes // Returns Routes type
func (e *Echo) Router() Router // Returns interface
```
**New Types:**
```go
type RouteInfo struct {
Name string
Method string
Path string
Parameters []string
}
type Routes []RouteInfo // Collection with helper methods
```
**Impact:** 🔴 **BREAKING CHANGE**
- Route registration methods return `RouteInfo` instead of `*Route`
- New `Routes` collection type with filtering methods
- `Route` struct still exists but used differently
---
### 5. **Response Type Changed**
**v4:**
```go
func (c Context) Response() *Response
type Response struct {
Writer http.ResponseWriter
Status int
Size int64
Committed bool
}
func NewResponse(w http.ResponseWriter, e *Echo) *Response
```
**v5:**
```go
func (c *Context) Response() http.ResponseWriter
type Response struct {
http.ResponseWriter // Embedded
Status int
Size int64
Committed bool
}
func NewResponse(w http.ResponseWriter, logger *slog.Logger) *Response
func UnwrapResponse(rw http.ResponseWriter) (*Response, error)
```
**Changes:**
- Context.Response() returns `http.ResponseWriter` instead of `*Response`
- Response now embeds `http.ResponseWriter`
- NewResponse takes `*slog.Logger` instead of `*Echo`
- New `UnwrapResponse()` helper function
---
### 6. **HTTPError Simplified**
**v4:**
```go
type HTTPError struct {
Internal error
Message interface{} // Can be any type
Code int
}
func NewHTTPError(code int, message ...interface{}) *HTTPError
```
**v5:**
```go
type HTTPError struct {
Code int
Message string // Now string only
// Has unexported fields (Internal moved)
}
func NewHTTPError(code int, message string) *HTTPError
func (he HTTPError) Wrap(err error) error // New method
func (he *HTTPError) StatusCode() int // Implements HTTPStatusCoder
```
**Changes:**
- `Message` field changed from `interface{}` to `string`
- `NewHTTPError()` now takes `string` instead of `...interface{}`
- Added `HTTPStatusCoder` interface and `StatusCode()` method
- Added `Wrap(err error)` method for error wrapping
---
### 7. **HTTPErrorHandler Signature Changed**
**v4:**
```go
type HTTPErrorHandler func(err error, c Context)
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context)
```
**v5:**
```go
type HTTPErrorHandler func(c *Context, err error) // Parameters swapped!
func DefaultHTTPErrorHandler(exposeError bool) HTTPErrorHandler // Now a factory
```
**Impact:** 🔴 **BREAKING CHANGE**
- Parameter order reversed: `(c *Context, err error)` instead of `(err error, c Context)`
- DefaultHTTPErrorHandler is now a factory function that returns HTTPErrorHandler
- Takes `exposeError` bool to control error message exposure
---
## Notable API Changes in v5
### 1. **Generic Parameter Extraction Functions (Updated Signatures)**
These helpers keep the same generic API but now accept `*Context`, and the
form helpers are renamed from `FormParam*` to `FormValue*`:
```go
// Query Parameters
func QueryParam[T any](c *Context, key string, opts ...any) (T, error)
func QueryParamOr[T any](c *Context, key string, defaultValue T, opts ...any) (T, error)
func QueryParams[T any](c *Context, key string, opts ...any) ([]T, error)
func QueryParamsOr[T any](c *Context, key string, defaultValue []T, opts ...any) ([]T, error)
// Path Parameters
func PathParam[T any](c *Context, paramName string, opts ...any) (T, error)
func PathParamOr[T any](c *Context, paramName string, defaultValue T, opts ...any) (T, error)
// Form Values
func FormValue[T any](c *Context, key string, opts ...any) (T, error)
func FormValueOr[T any](c *Context, key string, defaultValue T, opts ...any) (T, error)
func FormValues[T any](c *Context, key string, opts ...any) ([]T, error)
func FormValuesOr[T any](c *Context, key string, defaultValue []T, opts ...any) ([]T, error)
// Generic Parsing
func ParseValue[T any](value string, opts ...any) (T, error)
func ParseValueOr[T any](value string, defaultValue T, opts ...any) (T, error)
func ParseValues[T any](values []string, opts ...any) ([]T, error)
func ParseValuesOr[T any](values []string, defaultValue []T, opts ...any) ([]T, error)
```
`FormParam*` was renamed to `FormValue*`; the rest keep names but now take `*Context`.
**Supported Types:**
- bool, string
- int, int8, int16, int32, int64
- uint, uint8, uint16, uint32, uint64
- float32, float64
- time.Time, time.Duration
- BindUnmarshaler, encoding.TextUnmarshaler, json.Unmarshaler
**Example Usage:**
```go
// v5 - Type-safe parameter binding
id, err := echo.PathParam[int](c, "id")
page, err := echo.QueryParamOr[int](c, "page", 1)
tags, err := echo.QueryParams[string](c, "tags")
```
---
### 2. **Context Store Helpers Now Use `*Context`**
```go
// Type-safe context value retrieval
func ContextGet[T any](c *Context, key string) (T, error)
func ContextGetOr[T any](c *Context, key string, defaultValue T) (T, error)
// Error types
var ErrNonExistentKey = errors.New("non existent key")
var ErrInvalidKeyType = errors.New("invalid key type")
```
These helpers existed in v4 with `Context` and now accept `*Context`.
**Example:**
```go
// v5
user, err := echo.ContextGet[*User](c, "user")
count, err := echo.ContextGetOr[int](c, "count", 0)
```
---
### 3. **PathValues Type**
New structured path parameter handling:
```go
type PathValue struct {
Name string
Value string
}
type PathValues []PathValue
func (p PathValues) Get(name string) (string, bool)
func (p PathValues) GetOr(name string, defaultValue string) string
// Context methods
func (c *Context) PathValues() PathValues
func (c *Context) SetPathValues(pathValues PathValues)
```
---
### 4. **Time Parsing Options**
```go
type TimeLayout string
const (
TimeLayoutUnixTime = TimeLayout("UnixTime")
TimeLayoutUnixTimeMilli = TimeLayout("UnixTimeMilli")
TimeLayoutUnixTimeNano = TimeLayout("UnixTimeNano")
)
type TimeOpts struct {
Layout TimeLayout
ParseInLocation *time.Location
ToInLocation *time.Location
}
```
---
### 5. **StartConfig for Server Configuration**
```go
type StartConfig struct {
Address string
HideBanner bool
HidePort bool
CertFilesystem fs.FS
TLSConfig *tls.Config
ListenerNetwork string
ListenerAddrFunc func(addr net.Addr)
GracefulTimeout time.Duration
OnShutdownError func(err error)
BeforeServeFunc func(s *http.Server) error
}
func (sc StartConfig) Start(ctx context.Context, h http.Handler) error
func (sc StartConfig) StartTLS(ctx context.Context, h http.Handler, certFile, keyFile any) error
```
**Example:**
```go
// v5 - More control over server startup
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
sc := echo.StartConfig{
Address: ":8080",
GracefulTimeout: 10 * time.Second,
}
if err := sc.Start(ctx, e); err != nil {
log.Fatal(err)
}
```
---
### 6. **Echo Config and Constructors**
```go
type Config struct {
// Configuration for Echo (logger, binder, renderer, etc.)
}
func NewWithConfig(config Config) *Echo
```
This adds a configuration struct for creating an `Echo` instance without
mutating fields after `New()`.
---
### 7. **Enhanced Routing Features**
```go
// New route methods
func (e *Echo) AddRoute(route Route) (RouteInfo, error)
func (e *Echo) Middlewares() []MiddlewareFunc
func (e *Echo) PreMiddlewares() []MiddlewareFunc
type AddRouteError struct{ ... }
// Routes collection with filters
type Routes []RouteInfo
func (r Routes) Clone() Routes
func (r Routes) FilterByMethod(method string) (Routes, error)
func (r Routes) FilterByName(name string) (Routes, error)
func (r Routes) FilterByPath(path string) (Routes, error)
func (r Routes) FindByMethodPath(method string, path string) (RouteInfo, error)
func (r Routes) Reverse(routeName string, pathValues ...any) (string, error)
// RouteInfo operations
func (r RouteInfo) Clone() RouteInfo
func (r RouteInfo) Reverse(pathValues ...any) string
```
---
### 8. **Middleware Configuration Interface**
```go
type MiddlewareConfigurator interface {
ToMiddleware() (MiddlewareFunc, error)
}
```
Allows middleware configs to be converted to middleware without panicking.
---
### 9. **New Context Methods**
```go
// v5 additions
func (c *Context) FileFS(file string, filesystem fs.FS) error
func (c *Context) FormValueOr(name, defaultValue string) string
func (c *Context) InitializeRoute(ri *RouteInfo, pathValues *PathValues)
func (c *Context) ParamOr(name, defaultValue string) string
func (c *Context) QueryParamOr(name, defaultValue string) string
func (c *Context) RouteInfo() RouteInfo
```
---
### 10. **Virtual Host Support**
```go
func NewVirtualHostHandler(vhosts map[string]*Echo) *Echo
```
Creates an Echo instance that routes requests to different Echo instances based on host.
---
### 11. **New Binder Functions**
```go
func BindBody(c *Context, target any) error
func BindHeaders(c *Context, target any) error
func BindPathValues(c *Context, target any) error // Renamed from BindPathParams
func BindQueryParams(c *Context, target any) error
```
Top-level binding functions that work with `*Context`.
---
### 12. **New echotest Package**
```go
package echotest // import "github.com/labstack/echo/v5/echotest"
func LoadBytes(t *testing.T, name string, opts ...loadBytesOpts) []byte
func TrimNewlineEnd(bytes []byte) []byte
type ContextConfig struct{ ... }
type MultipartForm struct{ ... }
type MultipartFormFile struct{ ... }
```
Helpers for loading fixtures and constructing test contexts.
---
## Removed APIs in v5
### Constants
```go
// v4 - Removed in v5
const CONNECT = http.MethodConnect // Use http.MethodConnect directly
```
**Reason:** Deprecated in v4, use stdlib `http.Method*` constants instead.
---
### Constants Added in v5
```go
// v5 additions
const (
NotFoundRouteName = "echo_route_not_found_name"
)
```
---
### Error Variable Changes
**v4 exports:**
```go
ErrBadRequest
ErrInvalidKeyType
ErrNonExistentKey
```
**v5 exports:**
```go
ErrBadRequest // Now backed by unexported httpError type
ErrValidatorNotRegistered // New
ErrInvalidKeyType
ErrNonExistentKey
```
**Reason:** v5 centralizes on `NewHTTPError(code, message)` rather than a broad set
of predefined HTTP error variables.
---
### Functions Removed
```go
// v4 - Removed in v5
func GetPath(r *http.Request) string // Use r.URL.Path or r.URL.RawPath
```
### Variables Removed
```go
// v4 - Removed in v5
var MethodNotAllowedHandler = func(c Context) error { ... }
var NotFoundHandler = func(c Context) error { ... }
```
### Functions Renamed
```go
// v4
func FormParam[T any](c Context, key string, opts ...any) (T, error)
func FormParamOr[T any](c Context, key string, defaultValue T, opts ...any) (T, error)
func FormParams[T any](c Context, key string, opts ...any) ([]T, error)
func FormParamsOr[T any](c Context, key string, defaultValue []T, opts ...any) ([]T, error)
// v5
func FormValue[T any](c *Context, key string, opts ...any) (T, error)
func FormValueOr[T any](c *Context, key string, defaultValue T, opts ...any) (T, error)
func FormValues[T any](c *Context, key string, opts ...any) ([]T, error)
func FormValuesOr[T any](c *Context, key string, defaultValue []T, opts ...any) ([]T, error)
```
---
### Type Methods Removed/Changed
**Echo struct changes:**
```go
// v4 fields removed in v5
type Echo struct {
StdLogger *stdLog.Logger // Removed
Server *http.Server // Removed (use StartConfig)
TLSServer *http.Server // Removed (use StartConfig)
Listener net.Listener // Removed (use StartConfig)
TLSListener net.Listener // Removed (use StartConfig)
AutoTLSManager autocert.Manager // Removed
ListenerNetwork string // Removed
OnAddRouteHandler func(...) // Changed to OnAddRoute
DisableHTTP2 bool // Removed (use StartConfig)
Debug bool // Removed
HideBanner bool // Removed (use StartConfig)
HidePort bool // Removed (use StartConfig)
}
// v5 Echo struct (simplified)
type Echo struct {
Binder Binder
Filesystem fs.FS // NEW
Renderer Renderer
Validator Validator
JSONSerializer JSONSerializer
IPExtractor IPExtractor
OnAddRoute func(route Route) error // Simplified
HTTPErrorHandler HTTPErrorHandler
Logger *slog.Logger // Changed from Logger interface
}
```
---
**Context interface → struct:**
```go
// v4
type Context interface {
// Had: SetResponse(*Response)
Response() *Response
// Had: ParamNames(), SetParamNames(), ParamValues(), SetParamValues()
// These are removed in v5 (use PathValues() instead)
}
// v5
type Context struct {
// Concrete struct with unexported fields
}
func (c *Context) Response() http.ResponseWriter // Changed return type
func (c *Context) PathValues() PathValues // Replaces ParamNames/Values
```
---
**Types removed:**
```go
// v4
type Map map[string]interface{}
```
**Group changes:**
```go
// v4
func (g *Group) File(path, file string) // No return value
func (g *Group) Static(pathPrefix, fsRoot string) // No return value
func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS) // No return value
// v5
func (g *Group) File(path, file string, middleware ...MiddlewareFunc) RouteInfo
func (g *Group) Static(pathPrefix, fsRoot string, middleware ...MiddlewareFunc) RouteInfo
func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS, middleware ...MiddlewareFunc) RouteInfo
```
Now return `RouteInfo` and accept middleware.
---
### Value Binder Factory Name Changes
```go
// v4
func PathParamsBinder(c Context) *ValueBinder
func QueryParamsBinder(c Context) *ValueBinder
func FormFieldBinder(c Context) *ValueBinder
// v5
func PathValuesBinder(c *Context) *ValueBinder // Renamed
func QueryParamsBinder(c *Context) *ValueBinder
func FormFieldBinder(c *Context) *ValueBinder
```
---
## Type Signature Changes
### Binder Interface
```go
// v4
type Binder interface {
Bind(i interface{}, c Context) error
}
// v5
type Binder interface {
Bind(c *Context, target any) error // Parameters swapped!
}
```
---
### DefaultBinder Methods
```go
// v4
func (b *DefaultBinder) Bind(i interface{}, c Context) error
func (b *DefaultBinder) BindBody(c Context, i interface{}) error
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error
// v5
func (b *DefaultBinder) Bind(c *Context, target any) error // Swapped params
// BindBody, BindPathParams, etc. are now top-level functions
```
---
### JSONSerializer Interface
```go
// v4
type JSONSerializer interface {
Serialize(c Context, i interface{}, indent string) error
Deserialize(c Context, i interface{}) error
}
// v5
type JSONSerializer interface {
Serialize(c *Context, target any, indent string) error
Deserialize(c *Context, target any) error
}
```
---
### Renderer Interface
```go
// v4
type Renderer interface {
Render(io.Writer, string, interface{}, Context) error
}
// v5
type Renderer interface {
Render(c *Context, w io.Writer, templateName string, data any) error
}
```
Parameters reordered with Context first.
---
### NewBindingError
```go
// v4
func NewBindingError(sourceParam string, values []string, message interface{}, internalError error) error
// v5
func NewBindingError(sourceParam string, values []string, message string, err error) error
```
Message parameter changed from `interface{}` to `string`.
---
### HandlerName
```go
// v5 only
func HandlerName(h HandlerFunc) string
```
New utility function to get handler function name.
---
## Middleware Package Changes
### Signature and Type Updates
```go
// CORS now accepts optional allow-origins
func CORS(allowOrigins ...string) echo.MiddlewareFunc
// BodyLimit now accepts bytes
func BodyLimit(limitBytes int64) echo.MiddlewareFunc
// DefaultSkipper now uses *echo.Context
func DefaultSkipper(c *echo.Context) bool
// Trailing slash configs renamed/split
func AddTrailingSlashWithConfig(config AddTrailingSlashConfig) echo.MiddlewareFunc
func RemoveTrailingSlashWithConfig(config RemoveTrailingSlashConfig) echo.MiddlewareFunc
type AddTrailingSlashConfig struct{ ... }
type RemoveTrailingSlashConfig struct{ ... }
// Auth + extractor signatures now use *echo.Context and add ExtractorSource
type BasicAuthValidator func(c *echo.Context, user string, password string) (bool, error)
type Extractor func(c *echo.Context) (string, error)
type ExtractorSource string
type KeyAuthValidator func(c *echo.Context, key string, source ExtractorSource) (bool, error)
type KeyAuthErrorHandler func(c *echo.Context, err error) error
// BodyDump handler now includes err
type BodyDumpHandler func(c *echo.Context, reqBody []byte, resBody []byte, err error)
// ValuesExtractor now returns extractor source and CreateExtractors takes a limit
type ValuesExtractor func(c *echo.Context) ([]string, ExtractorSource, error)
func CreateExtractors(lookups string, limit uint) ([]ValuesExtractor, error)
type ValueExtractorError struct{ ... }
// New constants
const KB = 1024
// Rate limiter store now takes a float64 limit
func NewRateLimiterMemoryStore(rateLimit float64) (store *RateLimiterMemoryStore)
```
### Added Middleware Exports
```go
var ErrInvalidKey = echo.NewHTTPError(http.StatusUnauthorized, "invalid key")
var ErrKeyMissing = echo.NewHTTPError(http.StatusUnauthorized, "missing key")
var RedirectHTTPSConfig = RedirectConfig{ ... }
var RedirectHTTPSWWWConfig = RedirectConfig{ ... }
var RedirectNonHTTPSWWWConfig = RedirectConfig{ ... }
var RedirectNonWWWConfig = RedirectConfig{ ... }
var RedirectWWWConfig = RedirectConfig{ ... }
```
### Removed/Consolidated Middleware Exports
```go
// Removed in v5
func Logger() echo.MiddlewareFunc
func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc
func Timeout() echo.MiddlewareFunc
func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc
type ErrKeyAuthMissing struct{ ... }
type CSRFErrorHandler func(err error, c echo.Context) error
type LoggerConfig struct{ ... }
type LogErrorFunc func(c echo.Context, err error, stack []byte) error
type TargetProvider interface{ ... }
type TrailingSlashConfig struct{ ... }
type TimeoutConfig struct{ ... }
```
Also removed defaults: `DefaultBasicAuthConfig`, `DefaultBodyDumpConfig`, `DefaultBodyLimitConfig`,
`DefaultCORSConfig`, `DefaultDecompressConfig`, `DefaultGzipConfig`, `DefaultLoggerConfig`,
`DefaultRedirectConfig`, `DefaultRequestIDConfig`, `DefaultRewriteConfig`, `DefaultTimeoutConfig`,
`DefaultTrailingSlashConfig`.
---
## Router Interface Changes
### v4 Router (Concrete Struct)
```go
type Router struct { ... }
func NewRouter(e *Echo) *Router
func (r *Router) Add(method, path string, h HandlerFunc)
func (r *Router) Find(method, path string, c Context)
func (r *Router) Reverse(name string, params ...interface{}) string
func (r *Router) Routes() []*Route
```
### v5 Router (Interface + DefaultRouter)
```go
type Router interface {
Add(routable Route) (RouteInfo, error)
Remove(method string, path string) error
Routes() Routes
Route(c *Context) HandlerFunc
}
type DefaultRouter struct { ... }
func NewRouter(config RouterConfig) *DefaultRouter
func NewConcurrentRouter(r Router) Router // NEW
type RouterConfig struct {
NotFoundHandler HandlerFunc
MethodNotAllowedHandler HandlerFunc
OptionsMethodHandler HandlerFunc
AllowOverwritingRoute bool
UnescapePathParamValues bool
UseEscapedPathForMatching bool
}
```
**Key Changes:**
- Router is now an interface
- DefaultRouter is the concrete implementation
- Add() returns `(RouteInfo, error)` instead of being void
- New `Remove()` method
- New `Route()` method replaces `Find()`
- Configuration through `RouterConfig`
---
## Echo Instance Method Changes
### Route Registration
```go
// v4
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route
// v5
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) RouteInfo
func (e *Echo) AddRoute(route Route) (RouteInfo, error) // NEW
```
### Static File Serving
```go
// v4
func (e *Echo) Static(pathPrefix, fsRoot string) *Route
func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) *Route
func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route
func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route
// v5
func (e *Echo) Static(pathPrefix, fsRoot string, middleware ...MiddlewareFunc) RouteInfo
func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS, middleware ...MiddlewareFunc) RouteInfo
func (e *Echo) File(path, file string, middleware ...MiddlewareFunc) RouteInfo
func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) RouteInfo
```
Return type changed from `*Route` to `RouteInfo`.
### Server Management
```go
// v4
func (e *Echo) Start(address string) error
func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) error
func (e *Echo) StartAutoTLS(address string) error
func (e *Echo) StartH2CServer(address string, h2s *http2.Server) error
func (e *Echo) StartServer(s *http.Server) error
func (e *Echo) Shutdown(ctx context.Context) error
func (e *Echo) Close() error
func (e *Echo) ListenerAddr() net.Addr
func (e *Echo) TLSListenerAddr() net.Addr
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context)
// v5
func (e *Echo) Start(address string) error // Simplified
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request)
// Removed: StartTLS, StartAutoTLS, StartH2CServer, StartServer
// Use StartConfig instead for advanced server configuration
// Removed: Shutdown, Close, ListenerAddr, TLSListenerAddr
// Removed: DefaultHTTPErrorHandler (now a top-level factory function)
```
**v5 provides** `StartConfig` type for all advanced server configuration.
### Router Access
```go
// v4
func (e *Echo) Router() *Router
func (e *Echo) Routers() map[string]*Router // For multi-host
func (e *Echo) Routes() []*Route
func (e *Echo) Reverse(name string, params ...interface{}) string
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string
func (e *Echo) Host(name string, m ...MiddlewareFunc) *Group
// v5
func (e *Echo) Router() Router // Returns interface
// Removed: Routers(), Reverse(), URI(), URL(), Host()
// Use router.Routes() and Routes.Reverse() instead
```
---
## NewContext Changes
```go
// v4
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context
func NewResponse(w http.ResponseWriter, e *Echo) *Response
// v5
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) *Context
func NewContext(r *http.Request, w http.ResponseWriter, opts ...any) *Context // Standalone
func NewResponse(w http.ResponseWriter, logger *slog.Logger) *Response
```
---
## Migration Guide Summary
If you are using Linux you can migrate easier parts like that:
```bash
find . -type f -name "*.go" -exec sed -i 's/ echo.Context/ *echo.Context/g' {} +
find . -type f -name "*.go" -exec sed -i 's/echo\/v4/echo\/v5/g' {} +
```
or in your favorite IDE
Replace all:
1. ` echo.Context` -> ` *echo.Context`
2. `echo/v4` -> `echo/v5`
### 1. Update All Handler Signatures
```go
// Before
func MyHandler(c echo.Context) error { ... }
// After
func MyHandler(c *echo.Context) error { ... }
```
### 2. Update Logger Usage
```go
// Before
e.Logger.Info("Server started")
c.Logger().Error("Something went wrong")
// After
e.Logger.Info("Server started")
c.Logger().Error("Something went wrong") // Same API, different logger
```
### 3. Use Type-Safe Parameter Extraction
```go
// Before
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
// After
id, err := echo.PathParam[int](c, "id")
```
### 4. Update Error Handler
```go
// Before
e.HTTPErrorHandler = func(err error, c echo.Context) {
// handle error
}
// After
e.HTTPErrorHandler = func(c *echo.Context, err error) { // Swapped!
// handle error
}
// Or use factory
e.HTTPErrorHandler = echo.DefaultHTTPErrorHandler(true) // exposeError=true
```
### 5. Update Server Startup
```go
// Before
e.Start(":8080")
e.StartTLS(":443", "cert.pem", "key.pem")
// After
// Simple
e.Start(":8080")
// Advanced with graceful shutdown
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
sc := echo.StartConfig{Address: ":8080"}
sc.Start(ctx, e)
```
### 6. Update Route Info Access
```go
// Before
routes := e.Routes()
for _, r := range routes {
fmt.Println(r.Method, r.Path)
}
// After
routes := e.Router().Routes()
for _, r := range routes {
fmt.Println(r.Method, r.Path)
}
```
### 7. Update HTTPError Creation
```go
// Before
return echo.NewHTTPError(400, "invalid request", someDetail)
// After
return echo.NewHTTPError(400, "invalid request")
```
### 8. Update Custom Binder
```go
// Before
type MyBinder struct{}
func (b *MyBinder) Bind(i interface{}, c echo.Context) error { ... }
// After
type MyBinder struct{}
func (b *MyBinder) Bind(c *echo.Context, target any) error { ... } // Swapped!
```
### 9. Path Parameters
```go
// Before
names := c.ParamNames()
values := c.ParamValues()
// After
pathValues := c.PathValues()
for _, pv := range pathValues {
fmt.Println(pv.Name, pv.Value)
}
```
### 10. Response Access
```go
// Before
resp := c.Response()
resp.Header().Set("X-Custom", "value")
// After
c.Response().Header().Set("X-Custom", "value") // Returns http.ResponseWriter
// To get *echo.Response
resp, err := echo.UnwrapResponse(c.Response())
```
### Go Version Requirements
- **v4**: Go 1.24.0 (per `go.mod`)
- **v5**: Go 1.25.0 (per `go.mod`)
---
**Generated by comparing `go doc` output from master (v4.15.0) and v5 (v5.0.0-alpha) branches**
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## v5.0.4 - 2026-02-15
**Enhancements**
* Remove unused import 'errors' from README example by @kumapower17 in https://github.com/labstack/echo/pull/2889
* Fix Graceful shutdown: after `http.Server.Serve` returns we need to wait for graceful shutdown goroutine to finish by @aldas in https://github.com/labstack/echo/pull/2898
* Update location of oapi-codegen in README by @mromaszewicz in https://github.com/labstack/echo/pull/2896
* Add Go 1.26 to CI flow by @aldas in https://github.com/labstack/echo/pull/2899
* Add new function `echo.StatusCode` by @suwakei in https://github.com/labstack/echo/pull/2892
* CSRF: support older token-based CSRF protection handler that want to render token into template by @aldas in https://github.com/labstack/echo/pull/2894
* Add `echo.ResolveResponseStatus` function to help middleware/handlers determine HTTP status code and echo.Response by @aldas in https://github.com/labstack/echo/pull/2900
## v5.0.3 - 2026-02-06
**Security**
* Fix directory traversal vulnerability under Windows in Static middleware when default Echo filesystem is used. Reported by @shblue21.
This applies to cases when:
- Windows is used as OS
- `middleware.StaticConfig.Filesystem` is `nil` (default)
- `echo.Filesystem` is has not been set explicitly (default)
Exposure is restricted to the active process working directory and its subfolders.
## v5.0.2 - 2026-02-02
**Security**
* Fix Static middleware with `config.Browse=true` lists all files/subfolders from `config.Filesystem` root and not starting from `config.Root` in https://github.com/labstack/echo/pull/2887
## v5.0.1 - 2026-01-28
* Panic MW: will now return a custom PanicStackError with stack trace by @aldas in https://github.com/labstack/echo/pull/2871
* Docs: add missing err parameter to DenyHandler example by @cgalibern in https://github.com/labstack/echo/pull/2878
* improve: improve websocket checks in IsWebSocket() [per RFC 6455] by @raju-mechatronics in https://github.com/labstack/echo/pull/2875
* fix: Context.Json() should not send status code before serialization is complete by @aldas in https://github.com/labstack/echo/pull/2877
## v5.0.0 - 2026-01-18
Echo `v5` is maintenance release with **major breaking changes**
- `Context` is now struct instead of interface and we can add method to it in the future in minor versions.
- Adds new `Router` interface for possible new routing implementations.
- Drops old logging interface and uses moderm `log/slog` instead.
- Rearranges alot of methods/function signatures to make them more consistent.
Upgrade notes and `v4` support:
- Echo `v4` is supported with **security*** updates and **bug** fixes until **2026-12-31**
- If you are using Echo in a production environment, it is recommended to wait until after 2026-03-31 before upgrading.
- Until 2026-03-31, any critical issues requiring breaking `v5` API changes will be addressed, even if this violates semantic versioning.
See [API_CHANGES_V5.md](./API_CHANGES_V5.md) for public API changes between `v4` and `v5`, notes on **upgrading**.
Upgrading TLDR:
If you are using Linux you can migrate easier parts like that:
```bash
find . -type f -name "*.go" -exec sed -i 's/ echo.Context/ *echo.Context/g' {} +
find . -type f -name "*.go" -exec sed -i 's/echo\/v4/echo\/v5/g' {} +
```
macOS
```bash
find . -type f -name "*.go" -exec sed -i '' 's/ echo.Context/ *echo.Context/g' {} +
find . -type f -name "*.go" -exec sed -i '' 's/echo\/v4/echo\/v5/g' {} +
```
or in your favorite IDE
Replace all:
1. ` echo.Context` -> ` *echo.Context`
2. `echo/v4` -> `echo/v5`
This should solve most of the issues. Probably the hardest part is updating all the tests.
## v4.15.0 - 2026-01-01
**Security**
NB: **If your application relies on cross-origin or same-site (same subdomain) requests do not blindly push this version to production**
The CSRF middleware now supports the [**Sec-Fetch-Site**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site) header as a modern, defense-in-depth approach to [CSRF
protection](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#fetch-metadata-headers), implementing the OWASP-recommended Fetch Metadata API alongside the traditional token-based mechanism.
**How it works:**
Modern browsers automatically send the `Sec-Fetch-Site` header with all requests, indicating the relationship
between the request origin and the target. The middleware uses this to make security decisions:
- **`same-origin`** or **`none`**: Requests are allowed (exact origin match or direct user navigation)
- **`same-site`**: Falls back to token validation (e.g., subdomain to main domain)
- **`cross-site`**: Blocked by default with 403 error for unsafe methods (POST, PUT, DELETE, PATCH)
For browsers that don't send this header (older browsers), the middleware seamlessly falls back to
traditional token-based CSRF protection.
**New Configuration Options:**
- `TrustedOrigins []string`: Allowlist specific origins for cross-site requests (useful for OAuth callbacks, webhooks)
- `AllowSecFetchSiteFunc func(echo.Context) (bool, error)`: Custom logic for same-site/cross-site request validation
**Example:**
```go
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
// Allow OAuth callbacks from trusted provider
TrustedOrigins: []string{"https://oauth-provider.com"},
// Custom validation for same-site requests
AllowSecFetchSiteFunc: func(c echo.Context) (bool, error) {
// Your custom authorization logic here
return validateCustomAuth(c), nil
// return true, err // blocks request with error
// return true, nil // allows CSRF request through
// return false, nil // falls back to legacy token logic
},
}))
```
PR: https://github.com/labstack/echo/pull/2858
**Type-Safe Generic Parameter Binding**
* Added generic functions for type-safe parameter extraction and context access by @aldas in https://github.com/labstack/echo/pull/2856
Echo now provides generic functions for extracting path, query, and form parameters with automatic type conversion,
eliminating manual string parsing and type assertions.
**New Functions:**
- Path parameters: `PathParam[T]`, `PathParamOr[T]`
- Query parameters: `QueryParam[T]`, `QueryParamOr[T]`, `QueryParams[T]`, `QueryParamsOr[T]`
- Form values: `FormParam[T]`, `FormParamOr[T]`, `FormParams[T]`, `FormParamsOr[T]`
- Context store: `ContextGet[T]`, `ContextGetOr[T]`
**Supported Types:**
Primitives (`bool`, `string`, `int`/`uint` variants, `float32`/`float64`), `time.Duration`, `time.Time`
(with custom layouts and Unix timestamp support), and custom types implementing `BindUnmarshaler`,
`TextUnmarshaler`, or `JSONUnmarshaler`.
**Example:**
```go
// Before: Manual parsing
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
// After: Type-safe with automatic parsing
id, err := echo.PathParam[int](c, "id")
// With default values
page, err := echo.QueryParamOr[int](c, "page", 1)
limit, err := echo.QueryParamOr[int](c, "limit", 20)
// Type-safe context access (no more panics from type assertions)
user, err := echo.ContextGet[*User](c, "user")
```
PR: https://github.com/labstack/echo/pull/2856
**DEPRECATION NOTICE** Timeout Middleware Deprecated - Use ContextTimeout Instead
The `middleware.Timeout` middleware has been **deprecated** due to fundamental architectural issues that cause
data races. Use `middleware.ContextTimeout` or `middleware.ContextTimeoutWithConfig` instead.
**Why is this being deprecated?**
The Timeout middleware manipulates response writers across goroutine boundaries, which causes data races that
cannot be reliably fixed without a complete architectural redesign. The middleware:
- Swaps the response writer using `http.TimeoutHandler`
- Must be the first middleware in the chain (fragile constraint)
- Can cause races with other middleware (Logger, metrics, custom middleware)
- Has been the source of multiple race condition fixes over the years
**What should you use instead?**
The `ContextTimeout` middleware (available since v4.12.0) provides timeout functionality using Go's standard
context mechanism. It is:
- Race-free by design
- Can be placed anywhere in the middleware chain
- Simpler and more maintainable
- Compatible with all other middleware
**Migration Guide:**
```go
// Before (deprecated):
e.Use(middleware.Timeout())
// After (recommended):
e.Use(middleware.ContextTimeout(30 * time.Second))
```
**Important Behavioral Differences:**
1. **Handler cooperation required**: With ContextTimeout, your handlers must check `context.Done()` for cooperative
cancellation. The old Timeout middleware would send a 503 response regardless of handler cooperation, but had
data race issues.
2. **Error handling**: ContextTimeout returns errors through the standard error handling flow. Handlers that receive
`context.DeadlineExceeded` should handle it appropriately:
```go
e.GET("/long-task", func(c echo.Context) error {
ctx := c.Request().Context()
// Example: database query with context
result, err := db.QueryContext(ctx, "SELECT * FROM large_table")
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
// Handle timeout
return echo.NewHTTPError(http.StatusServiceUnavailable, "Request timeout")
}
return err
}
return c.JSON(http.StatusOK, result)
})
```
3. **Background tasks**: For long-running background tasks, use goroutines with context:
```go
e.GET("/async-task", func(c echo.Context) error {
ctx := c.Request().Context()
resultCh := make(chan Result, 1)
errCh := make(chan error, 1)
go func() {
result, err := performLongTask(ctx)
if err != nil {
errCh <- err
return
}
resultCh <- result
}()
select {
case result := <-resultCh:
return c.JSON(http.StatusOK, result)
case err := <-errCh:
return err
case <-ctx.Done():
return echo.NewHTTPError(http.StatusServiceUnavailable, "Request timeout")
}
})
```
**Enhancements**
* Fixes by @aldas in https://github.com/labstack/echo/pull/2852
* Generic functions by @aldas in https://github.com/labstack/echo/pull/2856
* CRSF with Sec-Fetch-Site checks by @aldas in https://github.com/labstack/echo/pull/2858
## v4.14.0 - 2025-12-11
`middleware.Logger` has been deprecated. For request logging, use `middleware.RequestLogger` or
`middleware.RequestLoggerWithConfig`.
`middleware.RequestLogger` replaces `middleware.Logger`, offering comparable configuration while relying on the
Go standard library’s new `slog` logger.
The previous default output format was JSON. The new default follows the standard `slog` logger settings.
To continue emitting request logs in JSON, configure `slog` accordingly:
```go
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
e.Use(middleware.RequestLogger())
```
**Security**
* Logger middleware json string escaping and deprecation by @aldas in https://github.com/labstack/echo/pull/2849
**Enhancements**
* Update deps by @aldas in https://github.com/labstack/echo/pull/2807
* refactor to use reflect.TypeFor by @cuiweixie in https://github.com/labstack/echo/pull/2812
* Use Go 1.25 in CI by @aldas in https://github.com/labstack/echo/pull/2810
* Modernize context.go by replacing interface{} with any by @vishr in https://github.com/labstack/echo/pull/2822
* Fix typo in SetParamValues comment by @vishr in https://github.com/labstack/echo/pull/2828
* Fix typo in ContextTimeout middleware comment by @vishr in https://github.com/labstack/echo/pull/2827
* Improve BasicAuth middleware: use strings.Cut and RFC compliance by @vishr in https://github.com/labstack/echo/pull/2825
* Fix duplicate plus operator in router backtracking logic by @yuya-morimoto in https://github.com/labstack/echo/pull/2832
* Replace custom private IP range check with built-in net.IP.IsPrivate by @kumapower17 in https://github.com/labstack/echo/pull/2835
* Ensure proxy connection is closed in proxyRaw function(#2837) by @kumapower17 in https://github.com/labstack/echo/pull/2838
* Update deps by @aldas in https://github.com/labstack/echo/pull/2843
* Update golang.org/x/* deps by @aldas in https://github.com/labstack/echo/pull/2850
## v4.13.4 - 2025-05-22
**Enhancements**
* chore: fix some typos in comment by @zhuhaicity in https://github.com/labstack/echo/pull/2735
* CI: test with Go 1.24 by @aldas in https://github.com/labstack/echo/pull/2748
* Add support for TLS WebSocket proxy by @t-ibayashi-safie in https://github.com/labstack/echo/pull/2762
**Security**
* Update dependencies for [GO-2025-3487](https://pkg.go.dev/vuln/GO-2025-3487), [GO-2025-3503](https://pkg.go.dev/vuln/GO-2025-3503) and [GO-2025-3595](https://pkg.go.dev/vuln/GO-2025-3595) in https://github.com/labstack/echo/pull/2780
## v4.13.3 - 2024-12-19
**Security**
* Update golang.org/x/net dependency [GO-2024-3333](https://pkg.go.dev/vuln/GO-2024-3333) in https://github.com/labstack/echo/pull/2722
## v4.13.2 - 2024-12-12
**Security**
* Update dependencies (dependabot reports [GO-2024-3321](https://pkg.go.dev/vuln/GO-2024-3321)) in https://github.com/labstack/echo/pull/2721
## v4.13.1 - 2024-12-11
**Fixes**
* Fix BindBody ignoring `Transfer-Encoding: chunked` requests by @178inaba in https://github.com/labstack/echo/pull/2717
## v4.13.0 - 2024-12-04
**BREAKING CHANGE** JWT Middleware Removed from Core use [labstack/echo-jwt](https://github.com/labstack/echo-jwt) instead
The JWT middleware has been **removed from Echo core** due to another security vulnerability, [CVE-2024-51744](https://nvd.nist.gov/vuln/detail/CVE-2024-51744). For more details, refer to issue [#2699](https://github.com/labstack/echo/issues/2699). A drop-in replacement is available in the [labstack/echo-jwt](https://github.com/labstack/echo-jwt) repository.
**Important**: Direct assignments like `token := c.Get("user").(*jwt.Token)` will now cause a panic due to an invalid cast. Update your code accordingly. Replace the current imports from `"github.com/golang-jwt/jwt"` in your handlers to the new middleware version using `"github.com/golang-jwt/jwt/v5"`.
Background:
The version of `golang-jwt/jwt` (v3.2.2) previously used in Echo core has been in an unmaintained state for some time. This is not the first vulnerability affecting this library; earlier issues were addressed in [PR #1946](https://github.com/labstack/echo/pull/1946).
JWT middleware was marked as deprecated in Echo core as of [v4.10.0](https://github.com/labstack/echo/releases/tag/v4.10.0) on 2022-12-27. If you did not notice that, consider leveraging tools like [Staticcheck](https://staticcheck.dev/) to catch such deprecations earlier in you dev/CI flow. For bonus points - check out [gosec](https://github.com/securego/gosec).
We sincerely apologize for any inconvenience caused by this change. While we strive to maintain backward compatibility within Echo core, recurring security issues with third-party dependencies have forced this decision.
**Enhancements**
* remove jwt middleware by @stevenwhitehead in https://github.com/labstack/echo/pull/2701
* optimization: struct alignment by @behnambm in https://github.com/labstack/echo/pull/2636
* bind: Maintain backwards compatibility for map[string]interface{} binding by @thesaltree in https://github.com/labstack/echo/pull/2656
* Add Go 1.23 to CI by @aldas in https://github.com/labstack/echo/pull/2675
* improve `MultipartForm` test by @martinyonatann in https://github.com/labstack/echo/pull/2682
* `bind` : add support of multipart multi files by @martinyonatann in https://github.com/labstack/echo/pull/2684
* Add TemplateRenderer struct to ease creating renderers for `html/template` and `text/template` packages. by @aldas in https://github.com/labstack/echo/pull/2690
* Refactor TestBasicAuth to utilize table-driven test format by @ErikOlson in https://github.com/labstack/echo/pull/2688
* Remove broken header by @aldas in https://github.com/labstack/echo/pull/2705
* fix(bind body): content-length can be -1 by @phamvinhdat in https://github.com/labstack/echo/pull/2710
* CORS middleware should compile allowOrigin regexp at creation by @aldas in https://github.com/labstack/echo/pull/2709
* Shorten Github issue template and add test example by @aldas in https://github.com/labstack/echo/pull/2711
## v4.12.0 - 2024-04-15
**Security**
* Update golang.org/x/net dep because of [GO-2024-2687](https://pkg.go.dev/vuln/GO-2024-2687) by @aldas in https://github.com/labstack/echo/pull/2625
**Enhancements**
* binder: make binding to Map work better with string destinations by @aldas in https://github.com/labstack/echo/pull/2554
* README.md: add Encore as sponsor by @marcuskohlberg in https://github.com/labstack/echo/pull/2579
* Reorder paragraphs in README.md by @aldas in https://github.com/labstack/echo/pull/2581
* CI: upgrade actions/checkout to v4 by @aldas in https://github.com/labstack/echo/pull/2584
* Remove default charset from 'application/json' Content-Type header by @doortts in https://github.com/labstack/echo/pull/2568
* CI: Use Go 1.22 by @aldas in https://github.com/labstack/echo/pull/2588
* binder: allow binding to a nil map by @georgmu in https://github.com/labstack/echo/pull/2574
* Add Skipper Unit Test In BasicBasicAuthConfig and Add More Detail Explanation regarding BasicAuthValidator by @RyoKusnadi in https://github.com/labstack/echo/pull/2461
* fix some typos by @teslaedison in https://github.com/labstack/echo/pull/2603
* fix: some typos by @pomadev in https://github.com/labstack/echo/pull/2596
* Allow ResponseWriters to unwrap writers when flushing/hijacking by @aldas in https://github.com/labstack/echo/pull/2595
* Add SPDX licence comments to files. by @aldas in https://github.com/labstack/echo/pull/2604
* Upgrade deps by @aldas in https://github.com/labstack/echo/pull/2605
* Change type definition blocks to single declarations. This helps copy… by @aldas in https://github.com/labstack/echo/pull/2606
* Fix Real IP logic by @cl-bvl in https://github.com/labstack/echo/pull/2550
* Default binder can use `UnmarshalParams(params []string) error` inter… by @aldas in https://github.com/labstack/echo/pull/2607
* Default binder can bind pointer to slice as struct field. For example `*[]string` by @aldas in https://github.com/labstack/echo/pull/2608
* Remove maxparam dependence from Context by @aldas in https://github.com/labstack/echo/pull/2611
* When route is registered with empty path it is normalized to `/`. by @aldas in https://github.com/labstack/echo/pull/2616
* proxy middleware should use httputil.ReverseProxy for SSE requests by @aldas in https://github.com/labstack/echo/pull/2624
## v4.11.4 - 2023-12-20
**Security**
* Upgrade golang.org/x/crypto to v0.17.0 to fix vulnerability [issue](https://pkg.go.dev/vuln/GO-2023-2402) [#2562](https://github.com/labstack/echo/pull/2562)
**Enhancements**
* Update deps and mark Go version to 1.18 as this is what golang.org/x/* use [#2563](https://github.com/labstack/echo/pull/2563)
* Request logger: add example for Slog https://pkg.go.dev/log/slog [#2543](https://github.com/labstack/echo/pull/2543)
## v4.11.3 - 2023-11-07
**Security**
* 'c.Attachment' and 'c.Inline' should escape filename in 'Content-Disposition' header to avoid 'Reflect File Download' vulnerability. [#2541](https://github.com/labstack/echo/pull/2541)
**Enhancements**
* Tests: refactor context tests to be separate functions [#2540](https://github.com/labstack/echo/pull/2540)
* Proxy middleware: reuse echo request context [#2537](https://github.com/labstack/echo/pull/2537)
* Mark unmarshallable yaml struct tags as ignored [#2536](https://github.com/labstack/echo/pull/2536)
## v4.11.2 - 2023-10-11
**Security**
* Bump golang.org/x/net to prevent CVE-2023-39325 / CVE-2023-44487 HTTP/2 Rapid Reset Attack [#2527](https://github.com/labstack/echo/pull/2527)
* fix(sec): randomString bias introduced by #2490 [#2492](https://github.com/labstack/echo/pull/2492)
* CSRF/RequestID mw: switch math/random usage to crypto/random [#2490](https://github.com/labstack/echo/pull/2490)
**Enhancements**
* Delete unused context in body_limit.go [#2483](https://github.com/labstack/echo/pull/2483)
* Use Go 1.21 in CI [#2505](https://github.com/labstack/echo/pull/2505)
* Fix some typos [#2511](https://github.com/labstack/echo/pull/2511)
* Allow CORS middleware to send Access-Control-Max-Age: 0 [#2518](https://github.com/labstack/echo/pull/2518)
* Bump dependancies [#2522](https://github.com/labstack/echo/pull/2522)
## v4.11.1 - 2023-07-16
**Fixes**
* Fix `Gzip` middleware not sending response code for no content responses (404, 301/302 redirects etc) [#2481](https://github.com/labstack/echo/pull/2481)
## v4.11.0 - 2023-07-14
**Fixes**
* Fixes the proxy middleware concurrency issue of calling the Next() proxy target on Round Robin Balancer [#2409](https://github.com/labstack/echo/pull/2409)
* Fix `group.RouteNotFound` not working when group has attached middlewares [#2411](https://github.com/labstack/echo/pull/2411)
* Fix global error handler return error message when message is an error [#2456](https://github.com/labstack/echo/pull/2456)
* Do not use global timeNow variables [#2477](https://github.com/labstack/echo/pull/2477)
**Enhancements**
* Added a optional config variable to disable centralized error handler in recovery middleware [#2410](https://github.com/labstack/echo/pull/2410)
* refactor: use `strings.ReplaceAll` directly [#2424](https://github.com/labstack/echo/pull/2424)
* Add support for Go1.20 `http.rwUnwrapper` to Response struct [#2425](https://github.com/labstack/echo/pull/2425)
* Check whether is nil before invoking centralized error handling [#2429](https://github.com/labstack/echo/pull/2429)
* Proper colon support in `echo.Reverse` method [#2416](https://github.com/labstack/echo/pull/2416)
* Fix misuses of a vs an in documentation comments [#2436](https://github.com/labstack/echo/pull/2436)
* Add link to slog.Handler library for Echo logging into README.md [#2444](https://github.com/labstack/echo/pull/2444)
* In proxy middleware Support retries of failed proxy requests [#2414](https://github.com/labstack/echo/pull/2414)
* gofmt fixes to comments [#2452](https://github.com/labstack/echo/pull/2452)
* gzip response only if it exceeds a minimal length [#2267](https://github.com/labstack/echo/pull/2267)
* Upgrade packages [#2475](https://github.com/labstack/echo/pull/2475)
## v4.10.2 - 2023-02-22
**Security**
* `filepath.Clean` behaviour has changed in Go 1.20 - adapt to it [#2406](https://github.com/labstack/echo/pull/2406)
* Add `middleware.CORSConfig.UnsafeWildcardOriginWithAllowCredentials` to make UNSAFE usages of wildcard origin + allow cretentials less likely [#2405](https://github.com/labstack/echo/pull/2405)
**Enhancements**
* Add more HTTP error values [#2277](https://github.com/labstack/echo/pull/2277)
## v4.10.1 - 2023-02-19
**Security**
* Upgrade deps due to the latest golang.org/x/net vulnerability [#2402](https://github.com/labstack/echo/pull/2402)
**Enhancements**
* Add new JWT repository to the README [#2377](https://github.com/labstack/echo/pull/2377)
* Return an empty string for ctx.path if there is no registered path [#2385](https://github.com/labstack/echo/pull/2385)
* Add context timeout middleware [#2380](https://github.com/labstack/echo/pull/2380)
* Update link to jaegertracing [#2394](https://github.com/labstack/echo/pull/2394)
## v4.10.0 - 2022-12-27
**Security**
* We are deprecating JWT middleware in this repository. Please use https://github.com/labstack/echo-jwt instead.
JWT middleware is moved to separate repository to allow us to bump/upgrade version of JWT implementation (`github.com/golang-jwt/jwt`) we are using
which we can not do in Echo core because this would break backwards compatibility guarantees we try to maintain.
* This minor version bumps minimum Go version to 1.17 (from 1.16) due `golang.org/x/` packages we depend on. There are
several vulnerabilities fixed in these libraries.
Echo still tries to support last 4 Go versions but there are occasions we can not guarantee this promise.
**Enhancements**
* Bump x/text to 0.3.8 [#2305](https://github.com/labstack/echo/pull/2305)
* Bump dependencies and add notes about Go releases we support [#2336](https://github.com/labstack/echo/pull/2336)
* Add helper interface for ProxyBalancer interface [#2316](https://github.com/labstack/echo/pull/2316)
* Expose `middleware.CreateExtractors` function so we can use it from echo-contrib repository [#2338](https://github.com/labstack/echo/pull/2338)
* Refactor func(Context) error to HandlerFunc [#2315](https://github.com/labstack/echo/pull/2315)
* Improve function comments [#2329](https://github.com/labstack/echo/pull/2329)
* Add new method HTTPError.WithInternal [#2340](https://github.com/labstack/echo/pull/2340)
* Replace io/ioutil package usages [#2342](https://github.com/labstack/echo/pull/2342)
* Add staticcheck to CI flow [#2343](https://github.com/labstack/echo/pull/2343)
* Replace relative path determination from proprietary to std [#2345](https://github.com/labstack/echo/pull/2345)
* Remove square brackets from ipv6 addresses in XFF (X-Forwarded-For header) [#2182](https://github.com/labstack/echo/pull/2182)
* Add testcases for some BodyLimit middleware configuration options [#2350](https://github.com/labstack/echo/pull/2350)
* Additional configuration options for RequestLogger and Logger middleware [#2341](https://github.com/labstack/echo/pull/2341)
* Add route to request log [#2162](https://github.com/labstack/echo/pull/2162)
* GitHub Workflows security hardening [#2358](https://github.com/labstack/echo/pull/2358)
* Add govulncheck to CI and bump dependencies [#2362](https://github.com/labstack/echo/pull/2362)
* Fix rate limiter docs [#2366](https://github.com/labstack/echo/pull/2366)
* Refactor how `e.Routes()` work and introduce `e.OnAddRouteHandler` callback [#2337](https://github.com/labstack/echo/pull/2337)
## v4.9.1 - 2022-10-12
**Fixes**
* Fix logger panicing (when template is set to empty) by bumping dependency version [#2295](https://github.com/labstack/echo/issues/2295)
**Enhancements**
* Improve CORS documentation [#2272](https://github.com/labstack/echo/pull/2272)
* Update readme about supported Go versions [#2291](https://github.com/labstack/echo/pull/2291)
* Tests: improve error handling on closing body [#2254](https://github.com/labstack/echo/pull/2254)
* Tests: refactor some of the assertions in tests [#2275](https://github.com/labstack/echo/pull/2275)
* Tests: refactor assertions [#2301](https://github.com/labstack/echo/pull/2301)
## v4.9.0 - 2022-09-04
**Security**
* Fix open redirect vulnerability in handlers serving static directories (e.Static, e.StaticFs, echo.StaticDirectoryHandler) [#2260](https://github.com/labstack/echo/pull/2260)
**Enhancements**
* Allow configuring ErrorHandler in CSRF middleware [#2257](https://github.com/labstack/echo/pull/2257)
* Replace HTTP method constants in tests with stdlib constants [#2247](https://github.com/labstack/echo/pull/2247)
## v4.8.0 - 2022-08-10
**Most notable things**
You can now add any arbitrary HTTP method type as a route [#2237](https://github.com/labstack/echo/pull/2237)
```go
e.Add("COPY", "/*", func(c echo.Context) error
return c.String(http.StatusOK, "OK COPY")
})
```
You can add custom 404 handler for specific paths [#2217](https://github.com/labstack/echo/pull/2217)
```go
e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
g := e.Group("/images")
g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
```
**Enhancements**
* Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to Valuebinder [#2127](https://github.com/labstack/echo/pull/2127)
* Refactor: body_limit middleware unit test [#2145](https://github.com/labstack/echo/pull/2145)
* Refactor: Timeout mw: rework how test waits for timeout. [#2187](https://github.com/labstack/echo/pull/2187)
* BasicAuth middleware returns 500 InternalServerError on invalid base64 strings but should return 400 [#2191](https://github.com/labstack/echo/pull/2191)
* Refactor: duplicated findStaticChild process at findChildWithLabel [#2176](https://github.com/labstack/echo/pull/2176)
* Allow different param names in different methods with same path scheme [#2209](https://github.com/labstack/echo/pull/2209)
* Add support for registering handlers for different 404 routes [#2217](https://github.com/labstack/echo/pull/2217)
* Middlewares should use errors.As() instead of type assertion on HTTPError [#2227](https://github.com/labstack/echo/pull/2227)
* Allow arbitrary HTTP method types to be added as routes [#2237](https://github.com/labstack/echo/pull/2237)
## v4.7.2 - 2022-03-16
**Fixes**
* Fix nil pointer exception when calling Start again after address binding error [#2131](https://github.com/labstack/echo/pull/2131)
* Fix CSRF middleware not being able to extract token from multipart/form-data form [#2136](https://github.com/labstack/echo/pull/2136)
* Fix Timeout middleware write race [#2126](https://github.com/labstack/echo/pull/2126)
**Enhancements**
* Recover middleware should not log panic for aborted handler [#2134](https://github.com/labstack/echo/pull/2134)
## v4.7.1 - 2022-03-13
**Fixes**
* Fix `e.Static`, `.File()`, `c.Attachment()` being picky with paths starting with `./`, `../` and `/` after 4.7.0 introduced echo.Filesystem support (Go1.16+) [#2123](https://github.com/labstack/echo/pull/2123)
**Enhancements**
* Remove some unused code [#2116](https://github.com/labstack/echo/pull/2116)
## v4.7.0 - 2022-03-01
**Enhancements**
* Add JWT, KeyAuth, CSRF multivalue extractors [#2060](https://github.com/labstack/echo/pull/2060)
* Add LogErrorFunc to recover middleware [#2072](https://github.com/labstack/echo/pull/2072)
* Add support for HEAD method query params binding [#2027](https://github.com/labstack/echo/pull/2027)
* Improve filesystem support with echo.FileFS, echo.StaticFS, group.FileFS, group.StaticFS [#2064](https://github.com/labstack/echo/pull/2064)
**Fixes**
* Fix X-Real-IP bug, improve tests [#2007](https://github.com/labstack/echo/pull/2007)
* Minor syntax fixes [#1994](https://github.com/labstack/echo/pull/1994), [#2102](https://github.com/labstack/echo/pull/2102), [#2102](https://github.com/labstack/echo/pull/2102)
**General**
* Add cache-control and connection headers [#2103](https://github.com/labstack/echo/pull/2103)
* Add Retry-After header constant [#2078](https://github.com/labstack/echo/pull/2078)
* Upgrade `go` directive in `go.mod` to 1.17 [#2049](https://github.com/labstack/echo/pull/2049)
* Add Pagoda [#2077](https://github.com/labstack/echo/pull/2077) and Souin [#2069](https://github.com/labstack/echo/pull/2069) to 3rd-party middlewares in README
## v4.6.3 - 2022-01-10
**Fixes**
* Fixed Echo version number in greeting message which was not incremented to `4.6.2` [#2066](https://github.com/labstack/echo/issues/2066)
## v4.6.2 - 2022-01-08
**Fixes**
* Fixed route containing escaped colon should be matchable but is not matched to request path [#2047](https://github.com/labstack/echo/pull/2047)
* Fixed a problem that returned wrong content-encoding when the gzip compressed content was empty. [#1921](https://github.com/labstack/echo/pull/1921)
* Update (test) dependencies [#2021](https://github.com/labstack/echo/pull/2021)
**Enhancements**
* Add support for configurable target header for the request_id middleware [#2040](https://github.com/labstack/echo/pull/2040)
* Change decompress middleware to use stream decompression instead of buffering [#2018](https://github.com/labstack/echo/pull/2018)
* Documentation updates
## v4.6.1 - 2021-09-26
**Enhancements**
* Add start time to request logger middleware values [#1991](https://github.com/labstack/echo/pull/1991)
## v4.6.0 - 2021-09-20
Introduced a new [request logger](https://github.com/labstack/echo/blob/master/middleware/request_logger.go) middleware
to help with cases when you want to use some other logging library in your application.
**Fixes**
* fix timeout middleware warning: superfluous response.WriteHeader [#1905](https://github.com/labstack/echo/issues/1905)
**Enhancements**
* Add Cookie to KeyAuth middleware's KeyLookup [#1929](https://github.com/labstack/echo/pull/1929)
* JWT middleware should ignore case of auth scheme in request header [#1951](https://github.com/labstack/echo/pull/1951)
* Refactor default error handler to return first if response is already committed [#1956](https://github.com/labstack/echo/pull/1956)
* Added request logger middleware which helps to use custom logger library for logging requests. [#1980](https://github.com/labstack/echo/pull/1980)
* Allow escaping of colon in route path so Google Cloud API "custom methods" could be implemented [#1988](https://github.com/labstack/echo/pull/1988)
## v4.5.0 - 2021-08-01
**Important notes**
A **BREAKING CHANGE** is introduced for JWT middleware users.
The JWT library used for the JWT middleware had to be changed from [github.com/dgrijalva/jwt-go](https://github.com/dgrijalva/jwt-go) to
[github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) due former library being unmaintained and affected by security
issues.
The [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) project is a drop-in replacement, but supports only the latest 2 Go versions.
So for JWT middleware users Go 1.15+ is required. For detailed information please read [#1940](https://github.com/labstack/echo/discussions/)
To change the library imports in all .go files in your project replace all occurrences of `dgrijalva/jwt-go` with `golang-jwt/jwt`.
For Linux CLI you can use:
```bash
find -type f -name "*.go" -exec sed -i "s/dgrijalva\/jwt-go/golang-jwt\/jwt/g" {} \;
go mod tidy
```
**Fixes**
* Change JWT library to `github.com/golang-jwt/jwt` [#1946](https://github.com/labstack/echo/pull/1946)
## v4.4.0 - 2021-07-12
**Fixes**
* Split HeaderXForwardedFor header only by comma [#1878](https://github.com/labstack/echo/pull/1878)
* Fix Timeout middleware Context propagation [#1910](https://github.com/labstack/echo/pull/1910)
**Enhancements**
* Bind data using headers as source [#1866](https://github.com/labstack/echo/pull/1866)
* Adds JWTConfig.ParseTokenFunc to JWT middleware to allow different libraries implementing JWT parsing. [#1887](https://github.com/labstack/echo/pull/1887)
* Adding tests for Echo#Host [#1895](https://github.com/labstack/echo/pull/1895)
* Adds RequestIDHandler function to RequestID middleware [#1898](https://github.com/labstack/echo/pull/1898)
* Allow for custom JSON encoding implementations [#1880](https://github.com/labstack/echo/pull/1880)
## v4.3.0 - 2021-05-08
**Important notes**
* Route matching has improvements for following cases:
1. Correctly match routes with parameter part as last part of route (with trailing backslash)
2. Considering handlers when resolving routes and search for matching http method handler
* Echo minimal Go version is now 1.13.
**Fixes**
* When url ends with slash first param route is the match [#1804](https://github.com/labstack/echo/pull/1812)
* Router should check if node is suitable as matching route by path+method and if not then continue search in tree [#1808](https://github.com/labstack/echo/issues/1808)
* Fix timeout middleware not writing response correctly when handler panics [#1864](https://github.com/labstack/echo/pull/1864)
* Fix binder not working with embedded pointer structs [#1861](https://github.com/labstack/echo/pull/1861)
* Add Go 1.16 to CI and drop 1.12 specific code [#1850](https://github.com/labstack/echo/pull/1850)
**Enhancements**
* Make KeyFunc public in JWT middleware [#1756](https://github.com/labstack/echo/pull/1756)
* Add support for optional filesystem to the static middleware [#1797](https://github.com/labstack/echo/pull/1797)
* Add a custom error handler to key-auth middleware [#1847](https://github.com/labstack/echo/pull/1847)
* Allow JWT token to be looked up from multiple sources [#1845](https://github.com/labstack/echo/pull/1845)
## v4.2.2 - 2021-04-07
**Fixes**
* Allow proxy middleware to use query part in rewrite (#1802)
* Fix timeout middleware not sending status code when handler returns an error (#1805)
* Fix Bind() when target is array/slice and path/query params complains bind target not being struct (#1835)
* Fix panic in redirect middleware on short host name (#1813)
* Fix timeout middleware docs (#1836)
## v4.2.1 - 2021-03-08
**Important notes**
Due to a datarace the config parameters for the newly added timeout middleware required a change.
See the [docs](https://echo.labstack.com/middleware/timeout).
A performance regression has been fixed, even bringing better performance than before for some routing scenarios.
**Fixes**
* Fix performance regression caused by path escaping (#1777, #1798, #1799, aldas)
* Avoid context canceled errors (#1789, clwluvw)
* Improve router to use on stack backtracking (#1791, aldas, stffabi)
* Fix panic in timeout middleware not being not recovered and cause application crash (#1794, aldas)
* Fix Echo.Serve() not serving on HTTP port correctly when TLSListener is used (#1785, #1793, aldas)
* Apply go fmt (#1788, Le0tk0k)
* Uses strings.Equalfold (#1790, rkilingr)
* Improve code quality (#1792, withshubh)
This release was made possible by our **contributors**:
aldas, clwluvw, lammel, Le0tk0k, maciej-jezierski, rkilingr, stffabi, withshubh
## v4.2.0 - 2021-02-11
**Important notes**
The behaviour for binding data has been reworked for compatibility with echo before v4.1.11 by
enforcing `explicit tagging` for processing parameters. This **may break** your code if you
expect combined handling of query/path/form params.
Please see the updated documentation for [request](https://echo.labstack.com/guide/request) and [binding](https://echo.labstack.com/guide/request)
The handling for rewrite rules has been slightly adjusted to expand `*` to a non-greedy `(.*?)` capture group. This is only relevant if multiple asterisks are used in your rules.
Please see [rewrite](https://echo.labstack.com/middleware/rewrite) and [proxy](https://echo.labstack.com/middleware/proxy) for details.
**Security**
* Fix directory traversal vulnerability for Windows (#1718, little-cui)
* Fix open redirect vulnerability with trailing slash (#1771,#1775 aldas,GeoffreyFrogeye)
**Enhancements**
* Add Echo#ListenerNetwork as configuration (#1667, pafuent)
* Add ability to change the status code using response beforeFuncs (#1706, RashadAnsari)
* Echo server startup to allow data race free access to listener address
* Binder: Restore pre v4.1.11 behaviour for c.Bind() to use query params only for GET or DELETE methods (#1727, aldas)
* Binder: Add separate methods to bind only query params, path params or request body (#1681, aldas)
* Binder: New fluent binder for query/path/form parameter binding (#1717, #1736, aldas)
* Router: Performance improvements for missed routes (#1689, pafuent)
* Router: Improve performance for Real-IP detection using IndexByte instead of Split (#1640, imxyb)
* Middleware: Support real regex rules for rewrite and proxy middleware (#1767)
* Middleware: New rate limiting middleware (#1724, iambenkay)
* Middleware: New timeout middleware implementation for go1.13+ (#1743, )
* Middleware: Allow regex pattern for CORS middleware (#1623, KlotzAndrew)
* Middleware: Add IgnoreBase parameter to static middleware (#1701, lnenad, iambenkay)
* Middleware: Add an optional custom function to CORS middleware to validate origin (#1651, curvegrid)
* Middleware: Support form fields in JWT middleware (#1704, rkfg)
* Middleware: Use sync.Pool for (de)compress middleware to improve performance (#1699, #1672, pafuent)
* Middleware: Add decompress middleware to support gzip compressed requests (#1687, arun0009)
* Middleware: Add ErrJWTInvalid for JWT middleware (#1627, juanbelieni)
* Middleware: Add SameSite mode for CSRF cookies to support iframes (#1524, pr0head)
**Fixes**
* Fix handling of special trailing slash case for partial prefix (#1741, stffabi)
* Fix handling of static routes with trailing slash (#1747)
* Fix Static files route not working (#1671, pwli0755, lammel)
* Fix use of caret(^) in regex for rewrite middleware (#1588, chotow)
* Fix Echo#Reverse for Any type routes (#1695, pafuent)
* Fix Router#Find panic with infinite loop (#1661, pafuent)
* Fix Router#Find panic fails on Param paths (#1659, pafuent)
* Fix DefaultHTTPErrorHandler with Debug=true (#1477, lammel)
* Fix incorrect CORS headers (#1669, ulasakdeniz)
* Fix proxy middleware rewritePath to use url with updated tests (#1630, arun0009)
* Fix rewritePath for proxy middleware to use escaped path in (#1628, arun0009)
* Remove unless defer (#1656, imxyb)
**General**
* New maintainers for Echo: Roland Lammel (@lammel) and Pablo Andres Fuente (@pafuent)
* Add GitHub action to compare benchmarks (#1702, pafuent)
* Binding query/path params and form fields to struct only works for explicit tags (#1729,#1734, aldas)
* Add support for Go 1.15 in CI (#1683, asahasrabuddhe)
* Add test for request id to remain unchanged if provided (#1719, iambenkay)
* Refactor echo instance listener access and startup to speed up testing (#1735, aldas)
* Refactor and improve various tests for binding and routing
* Run test workflow only for relevant changes (#1637, #1636, pofl)
* Update .travis.yml (#1662, santosh653)
* Update README.md with an recents framework benchmark (#1679, pafuent)
This release was made possible by **over 100 commits** from more than **20 contributors**:
asahasrabuddhe, aldas, AndrewKlotz, arun0009, chotow, curvegrid, iambenkay, imxyb,
juanbelieni, lammel, little-cui, lnenad, pafuent, pofl, pr0head, pwli, RashadAnsari,
rkfg, santosh653, segfiner, stffabi, ulasakdeniz
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## About This Project
Echo is a high performance, minimalist Go web framework. This is the main repository for Echo v4, which is available as a Go module at `github.com/labstack/echo/v4`.
## Development Commands
The project uses a Makefile for common development tasks:
- `make check` - Run linting, vetting, and race condition tests (default target)
- `make init` - Install required linting tools (golint, staticcheck)
- `make lint` - Run staticcheck and golint
- `make vet` - Run go vet
- `make test` - Run short tests
- `make race` - Run tests with race detector
- `make benchmark` - Run benchmarks
Example commands for development:
```bash
# Setup development environment
make init
# Run all checks (lint, vet, race)
make check
# Run specific tests
go test ./middleware/...
go test -race ./...
# Run benchmarks
make benchmark
```
## Code Architecture
### Core Components
**Echo Instance (`echo.go`)**
- The `Echo` struct is the top-level framework instance
- Contains router, middleware stacks, and server configuration
- Not goroutine-safe for mutations after server start
**Context (`context.go`)**
- The `Context` interface represents HTTP request/response context
- Provides methods for request/response handling, path parameters, data binding
- Core abstraction for request processing
**Router (`router.go`)**
- Radix tree-based HTTP router with smart route prioritization
- Supports static routes, parameterized routes (`/users/:id`), and wildcard routes (`/static/*`)
- Each HTTP method has its own routing tree
**Middleware (`middleware/`)**
- Extensive middleware system with 50+ built-in middlewares
- Middleware can be applied at Echo, Group, or individual route level
- Common middleware: Logger, Recover, CORS, JWT, Rate Limiting, etc.
### Key Patterns
**Middleware Chain**
- Pre-middleware runs before routing
- Regular middleware runs after routing but before handlers
- Middleware functions have signature `func(next echo.HandlerFunc) echo.HandlerFunc`
**Route Groups**
- Routes can be grouped with common prefixes and middleware
- Groups support nested sub-groups
- Defined in `group.go`
**Data Binding**
- Automatic binding of request data (JSON, XML, form) to Go structs
- Implemented in `binder.go` with support for custom binders
**Error Handling**
- Centralized error handling via `HTTPErrorHandler`
- Automatic panic recovery with stack traces
## File Organization
- Root directory: Core Echo functionality (echo.go, context.go, router.go, etc.)
- `middleware/`: All built-in middleware implementations
- `_test/`: Test fixtures and utilities
- `_fixture/`: Test data files
## Code Style
- Go code uses tabs for indentation (per .editorconfig)
- Follows standard Go conventions and formatting
- Uses gofmt, golint, and staticcheck for code quality
## Testing
- Standard Go testing with `testing` package
- Tests include unit tests, integration tests, and benchmarks
- Race condition testing is required (`make race`)
- Test files follow `*_test.go` naming convention
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2022 LabStack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
PKG := "github.com/labstack/echo"
PKG_LIST := $(shell go list ${PKG}/...)
.DEFAULT_GOAL := check
check: lint vet race ## Check project
init:
@go install golang.org/x/lint/golint@latest
@go install honnef.co/go/tools/cmd/staticcheck@latest
lint: ## Lint the files
@staticcheck ${PKG_LIST}
@golint -set_exit_status ${PKG_LIST}
vet: ## Vet the files
@go vet ${PKG_LIST}
test: ## Run tests
@go test -short ${PKG_LIST}
race: ## Run tests with data race detector
@go test -race ${PKG_LIST}
benchmark: ## Run benchmarks
@go test -run="-" -benchmem -bench=".*" ${PKG_LIST}
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
goversion ?= "1.25"
test_version: ## Run tests inside Docker with given version (defaults to 1.25 oldest supported). Example: make test_version goversion=1.25
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"
================================================
FILE: README.md
================================================
[](https://sourcegraph.com/github.com/labstack/echo?badge)
[](https://pkg.go.dev/github.com/labstack/echo/v4)
[](https://goreportcard.com/report/github.com/labstack/echo)
[](https://github.com/labstack/echo/actions)
[](https://codecov.io/gh/labstack/echo)
[](https://github.com/labstack/echo/discussions)
[](https://twitter.com/labstack)
[](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
## Echo
High performance, extensible, minimalist Go web framework.
* [Official website](https://echo.labstack.com)
* [Quick start](https://echo.labstack.com/docs/quick-start)
* [Middlewares](https://echo.labstack.com/docs/category/middleware)
Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions)
### Feature Overview
- Optimized HTTP router which smartly prioritize routes
- Build robust and scalable RESTful APIs
- Group APIs
- Extensible middleware framework
- Define middleware at root, group or route level
- Data binding for JSON, XML and form payload
- Handy functions to send variety of HTTP responses
- Centralized HTTP error handling
- Template rendering with any template engine
- Define your format for the logger
- Highly customizable
- Automatic TLS via Let’s Encrypt
- HTTP/2 support
## Sponsors
<div>
<a href="https://encore.dev" style="display: inline-flex; align-items: center; gap: 10px">
<img src="https://user-images.githubusercontent.com/78424526/214602214-52e0483a-b5fc-4d4c-b03e-0b7b23e012df.svg" height="28px" alt="encore icon"></img>
<b>Encore – the platform for building Go-based cloud backends</b>
</a>
</div>
<br/>
Click [here](https://github.com/sponsors/labstack) for more information on sponsorship.
## [Guide](https://echo.labstack.com/guide)
### Supported Echo versions
- Latest major version of Echo is `v5` as of 2026-01-18.
- Until 2026-03-31, any critical issues requiring breaking API changes will be addressed, even if this violates
semantic versioning.
- See [API_CHANGES_V5.md](./API_CHANGES_V5.md) for public API changes between `v4` and `v5`, notes on upgrading.
- If you are using Echo in a production environment, it is recommended to wait until after 2026-03-31 before
upgrading.
- Echo `v4` is supported with **security*** updates and **bug** fixes until **2026-12-31**
### Installation
```sh
// go get github.com/labstack/echo/{version}
go get github.com/labstack/echo/v5
```
Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with
older versions.
### Example
```go
package main
import (
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
"log/slog"
"net/http"
)
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.RequestLogger()) // use the RequestLogger middleware with slog logger
e.Use(middleware.Recover()) // recover panics as errors for proper error handling
// Routes
e.GET("/", hello)
// Start server
if err := e.Start(":8080"); err != nil {
slog.Error("failed to start server", "error", err)
}
}
// Handler
func hello(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
```
# Official middleware repositories
Following list of middleware is maintained by Echo team.
| Repository | Description |
|------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [github.com/labstack/echo-jwt](https://github.com/labstack/echo-jwt) | [JWT](https://github.com/golang-jwt/jwt) middleware |
| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [pprof](https://pkg.go.dev/net/http/pprof)) middlewares |
| [github.com/labstack/echo-opentelemetry](https://github.com/labstack/echo-opentelemetry) | [OpenTelemetry](https://opentelemetry.io/) middleware for tracing and metrics |
| [github.com/labstack/echo-prometheus](https://github.com/labstack/echo-prometheus) | [Prometheus](https://github.com/prometheus/client_golang/) middleware for Echo |
# Third-party middleware repositories
Be careful when adding 3rd party middleware. Echo teams does not have time or manpower to guarantee safety and quality
of middlewares in this list.
| Repository | Description |
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [oapi-codegen/oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator |
| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. |
| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. |
| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. |
| [github.com/samber/slog-echo](https://github.com/samber/slog-echo) | Go [slog](https://pkg.go.dev/golang.org/x/exp/slog) logging library wrapper for Echo logger interface. |
| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. |
| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo. |
| [github.com/go-woo/protoc-gen-echo](https://github.com/go-woo/protoc-gen-echo) | ProtoBuf generate Echo server side code |
Please send a PR to add your own library here.
## Contribute
**Use issues for everything**
- For a small change, just send a PR.
- For bigger changes open an issue for discussion before sending a PR.
- PR should have:
- Test case
- Documentation
- Example (If it makes sense)
- You can also contribute by:
- Reporting issues
- Suggesting new features or enhancements
- Improve/fix documentation
## Credits
- [Vishal Rana](https://github.com/vishr) (Author)
- [Nitin Rana](https://github.com/nr17) (Consultant)
- [Roland Lammel](https://github.com/lammel) (Maintainer)
- [Martti T.](https://github.com/aldas) (Maintainer)
- [Pablo Andres Fuente](https://github.com/pafuent) (Maintainer)
- [Contributors](https://github.com/labstack/echo/graphs/contributors)
## License
[MIT](https://github.com/labstack/echo/blob/master/LICENSE)
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
| Version | Supported |
|-----------|-------------------------------------|
| 5.x.x | :white_check_mark: |
| >= 4.15.x | :white_check_mark: until 2026.12.31 |
| < 4.15 | :x: |
## Reporting a Vulnerability
https://github.com/labstack/echo/security/advisories/new
or look for maintainers email(s) in commits and email them.
================================================
FILE: _fixture/_fixture/README.md
================================================
This directory is used for the static middleware test
================================================
FILE: _fixture/certs/README.md
================================================
To generate a valid certificate and private key use the following command:
```bash
# In OpenSSL ≥ 1.1.1
openssl req -x509 -newkey rsa:4096 -sha256 -days 9999 -nodes \
-keyout key.pem -out cert.pem -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:::1"
```
To check a certificate use the following command:
```bash
openssl x509 -in cert.pem -text
```
================================================
FILE: _fixture/certs/cert.pem
================================================
-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIUaTvDluaMf+VJgYHQ0HFTS3yuCHYwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIxMDIyNzIxMzQ0MVoXDTQ4MDcx
NDIxMzQ0MVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAnqyyAAnWFH2TH7Epj5yfZxYrBvizydZe1Wo/1WpGR2IK
QT+qIul5sEKX/ERqEOXsawSrL3fw9cuSM8Z2vD/57ZZdoSR7XIdVaMDEQenJ968a
HObu4D27uBQwIwrM5ELgnd+fC4gis64nIu+2GSfHumZXi7lLW7DbNm8oWkMqI6tY
2s2wx2hwGYNVJrwSn4WGnkzhQ5U5mkcsLELMx7GR0Qnv6P7sNGZVeqMU7awkcSpR
crKR1OUP7XCJkEq83WLHSx50+QZv7LiyDmGnujHevRbdSHlcFfHZtaufYat+qICe
S3XADwRQe/0VSsmja6u3DAHy7VmL8PNisAdkopQZrhiI9OvGrpGZffs9zn+s/jeX
N1bqVDihCMiEjqXMlHx2oj3AXrZTFxb7y7Ap9C07nf70lpxQWW9SjMYRF98JBiHF
eJbQkNVkmz6T8ielQbX0l46F2SGK98oyFCGNIAZBUdj5CcS1E6w/lk4t58/em0k7
3wFC5qg0g0wfIbNSmxljBNxnaBYUqyaaAJJhpaEoOebm4RYV58hQ0FbMfpnLnSh4
dYStsk6i1PumWoa7D45DTtxF3kH7TB3YOB5aWaNGAPQC1m4Qcd23YB5Rd/ABirSp
ux6/cFGosjSfJ/G+G0RhNUpmcbDJvFSOhD2WCuieVhCTAzp+VPIA9bSqD+InlT0C
AwEAAaOBgTB/MB0GA1UdDgQWBBQZyM//SvzYKokQZI/0MVGb6PkH+zAfBgNVHSME
GDAWgBQZyM//SvzYKokQZI/0MVGb6PkH+zAPBgNVHRMBAf8EBTADAQH/MCwGA1Ud
EQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG
9w0BAQsFAAOCAgEAKGAJQmQ/KLw8iMb5QsyxxAonVjJ1eDAhNM3GWdHpM0/GFamO
vVtATLQQldwDiZJvrsCQPEc8ctZ2Utvg/StLQ3+rZpsvt0+gcUlLJK61qguwYqb2
+T7VK5s7V/OyI/tsuboOW50Pka9vQHV+Z0aM06Yu+HNDAq/UTpEOb/3MQvZd6Ooy
PTpZtFb/+5jIQa1dIsfFWmpBxF0+wUd9GEkX3j7nekwoZfJ8Ze4GWYERZbOFpDAQ
rIHdthH5VJztnpQJmaKqzgIOF+Rurwlp5ecSC33xNNjDaYtuf/fiWnoKGhHVSBhT
61+0yxn3rTgh/Dsm95xY00rSX6lmcvI+kRNTUc8GGPz0ajBH6xyY7bNhfMjmnSW/
C/XTEDbTAhT7ndWC5vvzp7ZU0TvN+WY6A0f2kxSnnrEk6QRUvRtKkjAkmAFz8exi
ttBBW0I3E5HNIC5CYRimq/9z+3clM/P1KbNblwuC65bL+PZ+nzFnn5hFaK9eLPol
OwZQXv7IvAw8GfgLTrEUT7eBCQwe1IqesA7NTxF1BVwmNUb2XamvQZ7ly67QybRw
0uJq80XjpVjBWYTTQy1dsnC2OTKdqGsV9TVIDR+UGfIG9cxL70pEbiSH2AX+IDCy
i3kNIvpXgBliAyOjW6Hj1fv6dNfAat/hqEfnquWkfvcs3HNrG/InwpwNAUs=
-----END CERTIFICATE-----
================================================
FILE: _fixture/certs/key.pem
================================================
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCerLIACdYUfZMf
sSmPnJ9nFisG+LPJ1l7Vaj/VakZHYgpBP6oi6XmwQpf8RGoQ5exrBKsvd/D1y5Iz
xna8P/ntll2hJHtch1VowMRB6cn3rxoc5u7gPbu4FDAjCszkQuCd358LiCKzrici
77YZJ8e6ZleLuUtbsNs2byhaQyojq1jazbDHaHAZg1UmvBKfhYaeTOFDlTmaRyws
QszHsZHRCe/o/uw0ZlV6oxTtrCRxKlFyspHU5Q/tcImQSrzdYsdLHnT5Bm/suLIO
Yae6Md69Ft1IeVwV8dm1q59hq36ogJ5LdcAPBFB7/RVKyaNrq7cMAfLtWYvw82Kw
B2SilBmuGIj068aukZl9+z3Of6z+N5c3VupUOKEIyISOpcyUfHaiPcBetlMXFvvL
sCn0LTud/vSWnFBZb1KMxhEX3wkGIcV4ltCQ1WSbPpPyJ6VBtfSXjoXZIYr3yjIU
IY0gBkFR2PkJxLUTrD+WTi3nz96bSTvfAULmqDSDTB8hs1KbGWME3GdoFhSrJpoA
kmGloSg55ubhFhXnyFDQVsx+mcudKHh1hK2yTqLU+6ZahrsPjkNO3EXeQftMHdg4
HlpZo0YA9ALWbhBx3bdgHlF38AGKtKm7Hr9wUaiyNJ8n8b4bRGE1SmZxsMm8VI6E
PZYK6J5WEJMDOn5U8gD1tKoP4ieVPQIDAQABAoICAEHF2CsH6MOpofi7GT08cR7s
I33KTcxWngzc9ATk/qjMTO/rEf1Sxmx3zkR1n3nNtQhPcR5GG43nin0HwWQbKOCB
OeJ4GuKp/o9jiHbCEEQpQyvD1jUBofSV+bYs3e2ogy8t6OGA1tGgWPy0XMlkoff0
QEnczw3864FO5m0z9h2/Ax//r02ZTw5kUEG0KAwT709jEuVO0AfRhM/8CKKmSola
EyaDtSmrWbdyLlSuzJRUNFrVBno3UTjdM0iqkks6jN3ojBhFwNNhY/1uIXafAXNk
LOnD1JYMIHCb6X809VWnqvYgozIWWb5rlA3iM2mITmId1LLqMYX5fWj2R5LUzSek
H+XG+F9FIouTaL1ACoXr0zyeY5N5YJdyXYa1tThdW+axX9ZrnPgeiQrmxzKPIyb7
LLlVtNBQUg/t5tX80KyYjkNUu4j3oq/uBYPi0m//ovwMyi9bSbbyPT+cDXuXX5Bc
oY7wyn3evXX0c1R7vdJLZLkLu+ctVex/9hvMjeW/mMasDjLnqY7pF3Skct1SX5N2
U8YVU9bGvFpLEwM9lmi/T7bcv+zbmGPlfTsZiFrCsixPLn7sX7y5M4L8au8O0jh0
nHm/8rWVg1Qw0Hobg3tA8FjeMa8Sr2fYmkNLVKFzhuJLxknTJLaUbX5CymNqWP4H
OctvfSY0nSZ1eQpBkQaJAoIBAQDTb/NhYCfaJBLXHVMy/VYd7kWGZ+I87artcE/l
8u0pJ8XOP4kp0otFIumpHUFodysAeP6HrI79MuJB40fy91HzWZC+NrPufFFFuZ0z
Ld1o3Y5nAeoZmMlf1F12Oe3OQZy7nm9eNNkfeoVtKqDv4FhAqk+aoMor86HscKsR
C6HlZFdGc7kX0ylrQAXPq9KLhcvUU9oAUpbqTbhYK83IebRJgFDG45HkVo9SUHpF
dmCFSb91eZpRGpdfNLCuLiSu52TebayaUCnceeAt8SyeiChJ/TwWmRRDJS0QUv6h
s3Wdp+cx9ANoujA4XzAs8Fld5IZ4bcG5jjwD62/tJyWrCC5DAoIBAQDAHfHjrYCK
GHBrMj+MA7cK7fCJUn/iJLSLGgo2ANYF5oq9gaCwHCtKIyB9DN/KiY0JpJ6PWg+Q
9Difq23YXiJjNEBS5EFTu9UwWAr1RhSAegrfHxm0sDbcAx31NtDYvBsADCWQYmzc
KPfBshf5K4g/VCIj2VzC2CE6kNtdhqLU6AV2Pi1Tl1S82xWoAjHy91tDmlFQNWCj
B2ZnZ7tY9zuwDfeBBOVCPHICgl5Q4PrY1KEWEXiNxgbtkNmOPAsY9WSqgOsP9pWK
J924gdCCvovINzZtgRisxKth6Fkhra+VCsheg9SWvgR09Deo6CCoSwYxOSb0cjh2
oyX5Rb1kJ7Z/AoIBAQCX2iNVoBV/GcFeNXV3fXLH9ESCj0FwuNC1zp/TanDhyerK
gd8k5k2Xzcc66gP73vpHUJ6dGlVni4/r+ivGV9HHkF/f/LGlaiuEhBZel2YY1mZb
nIhg8dZOuNqW+mvMYlsKdHNPmW0GqpwBF0iWfu1jI+4gA7Kvdj6o7RIvH8eaVEJK
GvqoHcP1fvmteJ2yDtmhGMfMy4QPqtnmmS8l+CJ/V2SsMuyorXIpkBsAoFAZ6ilT
WY53CT4F5nWt4v39j7pl9SatfT1TV0SmOjvtb6Rf3zu0jyR6RMzkmHa/839ZRylI
OxPntzDCi7qxy7yjLmlVPJ6RgZGgzwqHrEHlX+65AoIBAQCEzu6d3x5B2N02LZli
eFr8MjqbI64GLiulEY5HgNJzZ8k3cjocJI0Ehj36VIEMaYRXSzbVkIO8SCgwsPiR
n5mUDNX+t441jV62Odbxcc3Qdw226rABieOSupDmKEu92GOt57e8FV5939BOVYhf
FunsJYQoViXbCEAIVYVgJSfBmNfVwuvgonfQyn8xErtm4/pyRGa71PqGGSKAj2Qi
/16CuVUFGtZFsLV76JW8wZqHdI4bTF6TW3cEmaLbwcRGL7W0bMSS13rO8/pBh3QW
PhUxhoGYt6rQHHEBkPa04nXDyZ10QRwgTSGVnBIyMK4KyTpxorm8OI2x7dzdcomX
iCCPAoIBAETwfr2JKPb/AzrKhhbZgU+sLVn3WH/nb68VheNEmGOzsqXaSHCR2NOq
/ow7bawjc8yUIhBRzokR4F/7jGolOmfdq0MYFb6/YokssKfv1ugxBhmvOxpZ6F6E
cERJ8Ex/ffQU053gLR/0ammddVuS1GR5I/jEdP0lJVh0xapoZNUlT5dWYCgo20hY
ZAmKpU+veyUn+5Li0pmm959vnLK5LJzEA5mpz3w1QPPtVwQs05dwmEV3CRAcCeeh
8sXp49WNCSW4I3BxuTZzRV845SGIFhZwgVV42PTp2LPKl2p6E7Bk8xpUCCvBpALp
QmA5yIMx+u2Jpr7fUsXEXEPTEhvjff0=
-----END PRIVATE KEY-----
================================================
FILE: _fixture/dist/private.txt
================================================
private file
================================================
FILE: _fixture/dist/public/assets/readme.md
================================================
readme in assets
================================================
FILE: _fixture/dist/public/assets/subfolder/subfolder.md
================================================
file inside subfolder
================================================
FILE: _fixture/dist/public/index.html
================================================
<h1>Hello from index</h1>
================================================
FILE: _fixture/dist/public/test.txt
================================================
test.txt contents
================================================
FILE: _fixture/folder/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Echo</title>
</head>
<body>
</body>
</html>
================================================
FILE: _fixture/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Echo</title>
</head>
<body>
</body>
</html>
================================================
FILE: bind.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"encoding"
"encoding/xml"
"errors"
"mime/multipart"
"net/http"
"reflect"
"strconv"
"strings"
"time"
)
// Binder is the interface that wraps the Bind method.
type Binder interface {
Bind(c *Context, target any) error
}
// DefaultBinder is the default implementation of the Binder interface.
type DefaultBinder struct{}
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
// Types that don't implement this, but do implement encoding.TextUnmarshaler
// will use that interface instead.
type BindUnmarshaler interface {
// UnmarshalParam decodes and assigns a value from an form or query param.
UnmarshalParam(param string) error
}
// bindMultipleUnmarshaler is used by binder to unmarshal multiple values from request at once to
// type implementing this interface. For example request could have multiple query fields `?a=1&a=2&b=test` in that case
// for `a` following slice `["1", "2"] will be passed to unmarshaller.
type bindMultipleUnmarshaler interface {
UnmarshalParams(params []string) error
}
// BindPathValues binds path parameter values to bindable object
func BindPathValues(c *Context, target any) error {
params := map[string][]string{}
for _, param := range c.PathValues() {
params[param.Name] = []string{param.Value}
}
if err := bindData(target, params, "param", nil); err != nil {
return ErrBadRequest.Wrap(err)
}
return nil
}
// BindQueryParams binds query params to bindable object
func BindQueryParams(c *Context, target any) error {
if err := bindData(target, c.QueryParams(), "query", nil); err != nil {
return ErrBadRequest.Wrap(err)
}
return nil
}
// BindBody binds request body contents to bindable object
// NB: then binding forms take note that this implementation uses standard library form parsing
// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm
// See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm
// See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm
func BindBody(c *Context, target any) (err error) {
req := c.Request()
if req.ContentLength == 0 {
return
}
// mediatype is found like `mime.ParseMediaType()` does it
base, _, _ := strings.Cut(req.Header.Get(HeaderContentType), ";")
mediatype := strings.TrimSpace(base)
switch mediatype {
case MIMEApplicationJSON:
if err = c.Echo().JSONSerializer.Deserialize(c, target); err != nil {
var hErr *HTTPError
if errors.As(err, &hErr) {
return err
}
return ErrBadRequest.Wrap(err)
}
case MIMEApplicationXML, MIMETextXML:
if err = xml.NewDecoder(req.Body).Decode(target); err != nil {
return ErrBadRequest.Wrap(err)
}
case MIMEApplicationForm:
params, err := c.FormValues()
if err != nil {
return ErrBadRequest.Wrap(err)
}
if err = bindData(target, params, "form", nil); err != nil {
return ErrBadRequest.Wrap(err)
}
case MIMEMultipartForm:
params, err := c.MultipartForm()
if err != nil {
return ErrBadRequest.Wrap(err)
}
if err = bindData(target, params.Value, "form", params.File); err != nil {
return ErrBadRequest.Wrap(err)
}
default:
return &HTTPError{Code: http.StatusUnsupportedMediaType}
}
return nil
}
// BindHeaders binds HTTP headers to a bindable object
func BindHeaders(c *Context, target any) error {
if err := bindData(target, c.Request().Header, "header", nil); err != nil {
return ErrBadRequest.Wrap(err)
}
return nil
}
// Bind implements the `Binder#Bind` function.
// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous
// step bound values. For single source binding use their own methods BindBody, BindQueryParams, BindPathValues.
func (b *DefaultBinder) Bind(c *Context, target any) error {
if err := BindPathValues(c, target); err != nil {
return err
}
// Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
// For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
// The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670)
method := c.Request().Method
if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
if err := BindQueryParams(c, target); err != nil {
return err
}
}
return BindBody(c, target)
}
// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
func bindData(destination any, data map[string][]string, tag string, dataFiles map[string][]*multipart.FileHeader) error {
if destination == nil || (len(data) == 0 && len(dataFiles) == 0) {
return nil
}
hasFiles := len(dataFiles) > 0
typ := reflect.TypeOf(destination).Elem()
val := reflect.ValueOf(destination).Elem()
// Support binding to limited Map destinations:
// - map[string][]string,
// - map[string]string <-- (binds first value from data slice)
// - map[string]any
// You are better off binding to struct but there are user who want this map feature. Source of data for these cases are:
// params,query,header,form as these sources produce string values, most of the time slice of strings, actually.
if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String {
k := typ.Elem().Kind()
isElemInterface := k == reflect.Interface
isElemString := k == reflect.String
isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String
if !(isElemSliceOfStrings || isElemString || isElemInterface) {
return nil
}
if val.IsNil() {
val.Set(reflect.MakeMap(typ))
}
for k, v := range data {
if isElemString {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
} else if isElemInterface {
// To maintain backward compatibility, we always bind to the first string value
// and not the slice of strings when dealing with map[string]any{}
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
} else {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
}
}
return nil
}
// !struct
if typ.Kind() != reflect.Struct {
if tag == "param" || tag == "query" || tag == "header" {
// incompatible type, data is probably to be found in the body
return nil
}
return errors.New("binding element must be a struct")
}
for i := 0; i < typ.NumField(); i++ { // iterate over all destination fields
typeField := typ.Field(i)
structField := val.Field(i)
if typeField.Anonymous {
if structField.Kind() == reflect.Ptr {
structField = structField.Elem()
}
}
if !structField.CanSet() {
continue
}
structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get(tag)
if typeField.Anonymous && structFieldKind == reflect.Struct && inputFieldName != "" {
// if anonymous struct with query/param/form tags, report an error
return errors.New("query/param/form tags are not allowed with anonymous struct field")
}
if inputFieldName == "" {
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contain fields with tags).
// structs that implement BindUnmarshaler are bound only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := bindData(structField.Addr().Interface(), data, tag, dataFiles); err != nil {
return err
}
}
// does not have explicit tag and is not an ordinary struct - so move to next field
continue
}
if hasFiles {
if ok, err := isFieldMultipartFile(structField.Type()); err != nil {
return err
} else if ok {
if ok := setMultipartFileHeaderTypes(structField, inputFieldName, dataFiles); ok {
continue
}
}
}
inputValue, exists := data[inputFieldName]
if !exists {
// Go json.Unmarshal supports case-insensitive binding. However the
// url params are bound case-sensitive which is inconsistent. To
// fix this we must check all of the map values in a
// case-insensitive search.
for k, v := range data {
if strings.EqualFold(k, inputFieldName) {
inputValue = v
exists = true
break
}
}
}
if !exists {
continue
}
// NOTE: algorithm here is not particularly sophisticated. It probably does not work with absurd types like `**[]*int`
// but it is smart enough to handle niche cases like `*int`,`*[]string`,`[]*int` .
// try unmarshalling first, in case we're dealing with an alias to an array type
if ok, err := unmarshalInputsToField(typeField.Type.Kind(), inputValue, structField); ok {
if err != nil {
return err
}
continue
}
formatTag := typeField.Tag.Get("format")
if ok, err := unmarshalInputToField(typeField.Type.Kind(), inputValue[0], structField, formatTag); ok {
if err != nil {
return err
}
continue
}
// we could be dealing with pointer to slice `*[]string` so dereference it. There are weird OpenAPI generators
// that could create struct fields like that.
if structFieldKind == reflect.Pointer {
structFieldKind = structField.Elem().Kind()
structField = structField.Elem()
}
if structFieldKind == reflect.Slice {
sliceOf := structField.Type().Elem().Kind()
numElems := len(inputValue)
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
for j := 0; j < numElems; j++ {
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
return err
}
}
structField.Set(slice)
continue
}
if err := setWithProperType(structFieldKind, inputValue[0], structField); err != nil {
return err
}
}
return nil
}
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
// But also call it here, in case we're dealing with an array of BindUnmarshalers
// Note: format tag not available in this context, so empty string is passed
if ok, err := unmarshalInputToField(valueKind, val, structField, ""); ok {
return err
}
switch valueKind {
case reflect.Ptr:
return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
case reflect.Int:
return setIntField(val, 0, structField)
case reflect.Int8:
return setIntField(val, 8, structField)
case reflect.Int16:
return setIntField(val, 16, structField)
case reflect.Int32:
return setIntField(val, 32, structField)
case reflect.Int64:
return setIntField(val, 64, structField)
case reflect.Uint:
return setUintField(val, 0, structField)
case reflect.Uint8:
return setUintField(val, 8, structField)
case reflect.Uint16:
return setUintField(val, 16, structField)
case reflect.Uint32:
return setUintField(val, 32, structField)
case reflect.Uint64:
return setUintField(val, 64, structField)
case reflect.Bool:
return setBoolField(val, structField)
case reflect.Float32:
return setFloatField(val, 32, structField)
case reflect.Float64:
return setFloatField(val, 64, structField)
case reflect.String:
structField.SetString(val)
default:
return errors.New("unknown type")
}
return nil
}
func unmarshalInputsToField(valueKind reflect.Kind, values []string, field reflect.Value) (bool, error) {
if valueKind == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
field = field.Elem()
}
fieldIValue := field.Addr().Interface()
unmarshaler, ok := fieldIValue.(bindMultipleUnmarshaler)
if !ok {
return false, nil
}
return true, unmarshaler.UnmarshalParams(values)
}
func unmarshalInputToField(valueKind reflect.Kind, val string, field reflect.Value, formatTag string) (bool, error) {
if valueKind == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
field = field.Elem()
}
fieldIValue := field.Addr().Interface()
// Handle time.Time with custom format tag
if formatTag != "" {
if _, isTime := fieldIValue.(*time.Time); isTime {
t, err := time.Parse(formatTag, val)
if err != nil {
return true, err
}
field.Set(reflect.ValueOf(t))
return true, nil
}
}
switch unmarshaler := fieldIValue.(type) {
case BindUnmarshaler:
return true, unmarshaler.UnmarshalParam(val)
case encoding.TextUnmarshaler:
return true, unmarshaler.UnmarshalText([]byte(val))
}
return false, nil
}
func setIntField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
}
intVal, err := strconv.ParseInt(value, 10, bitSize)
if err == nil {
field.SetInt(intVal)
}
return err
}
func setUintField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
}
uintVal, err := strconv.ParseUint(value, 10, bitSize)
if err == nil {
field.SetUint(uintVal)
}
return err
}
func setBoolField(value string, field reflect.Value) error {
if value == "" {
value = "false"
}
boolVal, err := strconv.ParseBool(value)
if err == nil {
field.SetBool(boolVal)
}
return err
}
func setFloatField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0.0"
}
floatVal, err := strconv.ParseFloat(value, bitSize)
if err == nil {
field.SetFloat(floatVal)
}
return err
}
var (
// NOT supported by bind as you can NOT check easily empty struct being actual file or not
multipartFileHeaderType = reflect.TypeFor[multipart.FileHeader]()
// supported by bind as you can check by nil value if file existed or not
multipartFileHeaderPointerType = reflect.TypeFor[*multipart.FileHeader]()
multipartFileHeaderSliceType = reflect.TypeFor[[]multipart.FileHeader]()
multipartFileHeaderPointerSliceType = reflect.TypeFor[[]*multipart.FileHeader]()
)
func isFieldMultipartFile(field reflect.Type) (bool, error) {
switch field {
case multipartFileHeaderPointerType,
multipartFileHeaderSliceType,
multipartFileHeaderPointerSliceType:
return true, nil
case multipartFileHeaderType:
return true, errors.New("binding to multipart.FileHeader struct is not supported, use pointer to struct")
default:
return false, nil
}
}
func setMultipartFileHeaderTypes(structField reflect.Value, inputFieldName string, files map[string][]*multipart.FileHeader) bool {
fileHeaders := files[inputFieldName]
if len(fileHeaders) == 0 {
return false
}
result := true
switch structField.Type() {
case multipartFileHeaderPointerSliceType:
structField.Set(reflect.ValueOf(fileHeaders))
case multipartFileHeaderSliceType:
headers := make([]multipart.FileHeader, len(fileHeaders))
for i, fileHeader := range fileHeaders {
headers[i] = *fileHeader
}
structField.Set(reflect.ValueOf(headers))
case multipartFileHeaderPointerType:
structField.Set(reflect.ValueOf(fileHeaders[0]))
default:
result = false
}
return result
}
================================================
FILE: bind_test.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
type bindTestStruct struct {
T Timestamp
GoT time.Time
PtrI16 *int16
PtrUI *uint
Tptr *Timestamp
PtrF32 *float32
PtrB *bool
PtrI32 *int32
GoTptr *time.Time
PtrI64 *int64
PtrI *int
PtrI8 *int8
PtrF64 *float64
PtrUI8 *uint8
PtrUI64 *uint64
PtrUI16 *uint16
PtrS *string
PtrUI32 *uint32
S string
cantSet string
DoesntExist string
SA StringArray
F64 float64
I int
UI64 uint64
UI uint
I64 int64
F32 float32
UI32 uint32
I32 int32
UI16 uint16
I16 int16
B bool
UI8 uint8
I8 int8
}
type bindTestStructWithTags struct {
T Timestamp `json:"T" form:"T"`
GoT time.Time `json:"GoT" form:"GoT"`
PtrI16 *int16 `json:"PtrI16" form:"PtrI16"`
PtrUI *uint `json:"PtrUI" form:"PtrUI"`
Tptr *Timestamp `json:"Tptr" form:"Tptr"`
PtrF32 *float32 `json:"PtrF32" form:"PtrF32"`
PtrB *bool `json:"PtrB" form:"PtrB"`
PtrI32 *int32 `json:"PtrI32" form:"PtrI32"`
GoTptr *time.Time `json:"GoTptr" form:"GoTptr"`
PtrI64 *int64 `json:"PtrI64" form:"PtrI64"`
PtrI *int `json:"PtrI" form:"PtrI"`
PtrI8 *int8 `json:"PtrI8" form:"PtrI8"`
PtrF64 *float64 `json:"PtrF64" form:"PtrF64"`
PtrUI8 *uint8 `json:"PtrUI8" form:"PtrUI8"`
PtrUI64 *uint64 `json:"PtrUI64" form:"PtrUI64"`
PtrUI16 *uint16 `json:"PtrUI16" form:"PtrUI16"`
PtrS *string `json:"PtrS" form:"PtrS"`
PtrUI32 *uint32 `json:"PtrUI32" form:"PtrUI32"`
S string `json:"S" form:"S"`
cantSet string
DoesntExist string `json:"DoesntExist" form:"DoesntExist"`
SA StringArray `json:"SA" form:"SA"`
F64 float64 `json:"F64" form:"F64"`
I int `json:"I" form:"I"`
UI64 uint64 `json:"UI64" form:"UI64"`
UI uint `json:"UI" form:"UI"`
I64 int64 `json:"I64" form:"I64"`
F32 float32 `json:"F32" form:"F32"`
UI32 uint32 `json:"UI32" form:"UI32"`
I32 int32 `json:"I32" form:"I32"`
UI16 uint16 `json:"UI16" form:"UI16"`
I16 int16 `json:"I16" form:"I16"`
B bool `json:"B" form:"B"`
UI8 uint8 `json:"UI8" form:"UI8"`
I8 int8 `json:"I8" form:"I8"`
}
type Timestamp time.Time
type TA []Timestamp
type StringArray []string
type Struct struct {
Foo string
}
type Bar struct {
Baz int `json:"baz" query:"baz"`
}
func (t *Timestamp) UnmarshalParam(src string) error {
ts, err := time.Parse(time.RFC3339, src)
*t = Timestamp(ts)
return err
}
func (a *StringArray) UnmarshalParam(src string) error {
*a = StringArray(strings.Split(src, ","))
return nil
}
func (s *Struct) UnmarshalParam(src string) error {
*s = Struct{
Foo: src,
}
return nil
}
func (t bindTestStruct) GetCantSet() string {
return t.cantSet
}
var values = map[string][]string{
"I": {"0"},
"PtrI": {"0"},
"I8": {"8"},
"PtrI8": {"8"},
"I16": {"16"},
"PtrI16": {"16"},
"I32": {"32"},
"PtrI32": {"32"},
"I64": {"64"},
"PtrI64": {"64"},
"UI": {"0"},
"PtrUI": {"0"},
"UI8": {"8"},
"PtrUI8": {"8"},
"UI16": {"16"},
"PtrUI16": {"16"},
"UI32": {"32"},
"PtrUI32": {"32"},
"UI64": {"64"},
"PtrUI64": {"64"},
"B": {"true"},
"PtrB": {"true"},
"F32": {"32.5"},
"PtrF32": {"32.5"},
"F64": {"64.5"},
"PtrF64": {"64.5"},
"S": {"test"},
"PtrS": {"test"},
"cantSet": {"test"},
"T": {"2016-12-06T19:09:05+01:00"},
"Tptr": {"2016-12-06T19:09:05+01:00"},
"GoT": {"2016-12-06T19:09:05+01:00"},
"GoTptr": {"2016-12-06T19:09:05+01:00"},
"ST": {"bar"},
}
// ptr return pointer to value. This is useful as `v := []*int8{&int8(1)}` will not compile
func ptr[T any](value T) *T {
return &value
}
func TestToMultipleFields(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?id=1&ID=2", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
type Root struct {
ID int64 `query:"id"`
Child2 struct {
ID int64
}
Child1 struct {
ID int64 `query:"id"`
}
}
u := new(Root)
err := c.Bind(u)
if assert.NoError(t, err) {
assert.Equal(t, int64(1), u.ID) // perfectly reasonable
assert.Equal(t, int64(1), u.Child1.ID) // untagged struct containing tagged field gets filled (by tag)
assert.Equal(t, int64(0), u.Child2.ID) // untagged struct containing untagged field should not be bind
}
}
func TestBindJSON(t *testing.T) {
testBindOkay(t, strings.NewReader(userJSON), nil, MIMEApplicationJSON)
testBindOkay(t, strings.NewReader(userJSON), dummyQuery, MIMEApplicationJSON)
testBindArrayOkay(t, strings.NewReader(usersJSON), nil, MIMEApplicationJSON)
testBindArrayOkay(t, strings.NewReader(usersJSON), dummyQuery, MIMEApplicationJSON)
testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
testBindError(t, strings.NewReader(userJSONInvalidType), MIMEApplicationJSON, &json.UnmarshalTypeError{})
}
func TestBindXML(t *testing.T) {
testBindOkay(t, strings.NewReader(userXML), nil, MIMEApplicationXML)
testBindOkay(t, strings.NewReader(userXML), dummyQuery, MIMEApplicationXML)
testBindArrayOkay(t, strings.NewReader(userXML), nil, MIMEApplicationXML)
testBindArrayOkay(t, strings.NewReader(userXML), dummyQuery, MIMEApplicationXML)
testBindError(t, strings.NewReader(invalidContent), MIMEApplicationXML, errors.New(""))
testBindError(t, strings.NewReader(userXMLConvertNumberError), MIMEApplicationXML, &strconv.NumError{})
testBindError(t, strings.NewReader(userXMLUnsupportedTypeError), MIMEApplicationXML, &xml.SyntaxError{})
testBindOkay(t, strings.NewReader(userXML), nil, MIMETextXML)
testBindOkay(t, strings.NewReader(userXML), dummyQuery, MIMETextXML)
testBindError(t, strings.NewReader(invalidContent), MIMETextXML, errors.New(""))
testBindError(t, strings.NewReader(userXMLConvertNumberError), MIMETextXML, &strconv.NumError{})
testBindError(t, strings.NewReader(userXMLUnsupportedTypeError), MIMETextXML, &xml.SyntaxError{})
}
func TestBindForm(t *testing.T) {
testBindOkay(t, strings.NewReader(userForm), nil, MIMEApplicationForm)
testBindOkay(t, strings.NewReader(userForm), dummyQuery, MIMEApplicationForm)
e := New()
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userForm))
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
req.Header.Set(HeaderContentType, MIMEApplicationForm)
err := c.Bind(&[]struct{ Field string }{})
assert.Error(t, err)
}
func TestBindQueryParams(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?id=1&name=Jon+Snow", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
u := new(user)
err := c.Bind(u)
if assert.NoError(t, err) {
assert.Equal(t, 1, u.ID)
assert.Equal(t, "Jon Snow", u.Name)
}
}
func TestBindQueryParamsCaseInsensitive(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?ID=1&NAME=Jon+Snow", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
u := new(user)
err := c.Bind(u)
if assert.NoError(t, err) {
assert.Equal(t, 1, u.ID)
assert.Equal(t, "Jon Snow", u.Name)
}
}
func TestBindQueryParamsCaseSensitivePrioritized(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?id=1&ID=2&NAME=Jon+Snow&name=Jon+Doe", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
u := new(user)
err := c.Bind(u)
if assert.NoError(t, err) {
assert.Equal(t, 1, u.ID)
assert.Equal(t, "Jon Doe", u.Name)
}
}
func TestBindHeaderParam(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("Name", "Jon Doe")
req.Header.Set("Id", "2")
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
u := new(user)
err := BindHeaders(c, u)
if assert.NoError(t, err) {
assert.Equal(t, 2, u.ID)
assert.Equal(t, "Jon Doe", u.Name)
}
}
func TestBindHeaderParamBadType(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("Id", "salamander")
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
u := new(user)
err := BindHeaders(c, u)
assert.Error(t, err)
httpErr, ok := err.(*HTTPError)
if assert.True(t, ok) {
assert.Equal(t, http.StatusBadRequest, httpErr.Code)
}
}
func TestBindUnmarshalParam(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?ts=2016-12-06T19:09:05Z&sa=one,two,three&ta=2016-12-06T19:09:05Z&ta=2016-12-06T19:09:05Z&ST=baz", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
result := struct {
T Timestamp `query:"ts"`
ST Struct
StWithTag struct {
Foo string `query:"st"`
}
TA []Timestamp `query:"ta"`
SA StringArray `query:"sa"`
}{}
err := c.Bind(&result)
ts := Timestamp(time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC))
if assert.NoError(t, err) {
// assert.Equal( Timestamp(reflect.TypeOf(&Timestamp{}), time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), result.T)
assert.Equal(t, ts, result.T)
assert.Equal(t, StringArray([]string{"one", "two", "three"}), result.SA)
assert.Equal(t, []Timestamp{ts, ts}, result.TA)
assert.Equal(t, Struct{""}, result.ST) // child struct does not have a field with matching tag
assert.Equal(t, "baz", result.StWithTag.Foo) // child struct has field with matching tag
}
}
func TestBindUnmarshalText(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?ts=2016-12-06T19:09:05Z&sa=one,two,three&ta=2016-12-06T19:09:05Z&ta=2016-12-06T19:09:05Z&ST=baz", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
result := struct {
T time.Time `query:"ts"`
ST Struct
TA []time.Time `query:"ta"`
SA StringArray `query:"sa"`
}{}
err := c.Bind(&result)
ts := time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)
if assert.NoError(t, err) {
// assert.Equal(t, Timestamp(reflect.TypeOf(&Timestamp{}), time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), result.T)
assert.Equal(t, ts, result.T)
assert.Equal(t, StringArray([]string{"one", "two", "three"}), result.SA)
assert.Equal(t, []time.Time{ts, ts}, result.TA)
assert.Equal(t, Struct{""}, result.ST) // field in child struct does not have tag
}
}
func TestBindUnmarshalParamPtr(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?ts=2016-12-06T19:09:05Z", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
result := struct {
Tptr *Timestamp `query:"ts"`
}{}
err := c.Bind(&result)
if assert.NoError(t, err) {
assert.Equal(t, Timestamp(time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), *result.Tptr)
}
}
func TestBindUnmarshalParamAnonymousFieldPtr(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?baz=1", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
result := struct {
*Bar
}{&Bar{}}
err := c.Bind(&result)
if assert.NoError(t, err) {
assert.Equal(t, 1, result.Baz)
}
}
func TestBindUnmarshalParamAnonymousFieldPtrNil(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?baz=1", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
result := struct {
*Bar
}{}
err := c.Bind(&result)
if assert.NoError(t, err) {
assert.Nil(t, result.Bar)
}
}
func TestBindUnmarshalParamAnonymousFieldPtrCustomTag(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, `/?bar={"baz":100}&baz=1`, nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
result := struct {
*Bar `json:"bar" query:"bar"`
}{&Bar{}}
err := c.Bind(&result)
assert.Contains(t, err.Error(), "query/param/form tags are not allowed with anonymous struct field")
}
func TestBindUnmarshalTextPtr(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/?ts=2016-12-06T19:09:05Z", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
result := struct {
Tptr *time.Time `query:"ts"`
}{}
err := c.Bind(&result)
if assert.NoError(t, err) {
assert.Equal(t, time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC), *result.Tptr)
}
}
func TestBindMultipartForm(t *testing.T) {
bodyBuffer := new(bytes.Buffer)
mw := multipart.NewWriter(bodyBuffer)
mw.WriteField("id", "1")
mw.WriteField("name", "Jon Snow")
mw.Close()
body := bodyBuffer.Bytes()
testBindOkay(t, bytes.NewReader(body), nil, mw.FormDataContentType())
testBindOkay(t, bytes.NewReader(body), dummyQuery, mw.FormDataContentType())
}
func TestBindUnsupportedMediaType(t *testing.T) {
testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
}
func TestDefaultBinder_bindDataToMap(t *testing.T) {
exampleData := map[string][]string{
"multiple": {"1", "2"},
"single": {"3"},
}
t.Run("ok, bind to map[string]string", func(t *testing.T) {
dest := map[string]string{}
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t,
map[string]string{
"multiple": "1",
"single": "3",
},
dest,
)
})
t.Run("ok, bind to map[string]string with nil map", func(t *testing.T) {
var dest map[string]string
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t,
map[string]string{
"multiple": "1",
"single": "3",
},
dest,
)
})
t.Run("ok, bind to map[string][]string", func(t *testing.T) {
dest := map[string][]string{}
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t,
map[string][]string{
"multiple": {"1", "2"},
"single": {"3"},
},
dest,
)
})
t.Run("ok, bind to map[string][]string with nil map", func(t *testing.T) {
var dest map[string][]string
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t,
map[string][]string{
"multiple": {"1", "2"},
"single": {"3"},
},
dest,
)
})
t.Run("ok, bind to map[string]interface", func(t *testing.T) {
dest := map[string]any{}
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t,
map[string]any{
"multiple": "1",
"single": "3",
},
dest,
)
})
t.Run("ok, bind to map[string]interface with nil map", func(t *testing.T) {
var dest map[string]any
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t,
map[string]any{
"multiple": "1",
"single": "3",
},
dest,
)
})
t.Run("ok, bind to map[string]int skips", func(t *testing.T) {
dest := map[string]int{}
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t, map[string]int{}, dest)
})
t.Run("ok, bind to map[string]int skips with nil map", func(t *testing.T) {
var dest map[string]int
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t, map[string]int(nil), dest)
})
t.Run("ok, bind to map[string][]int skips", func(t *testing.T) {
dest := map[string][]int{}
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t, map[string][]int{}, dest)
})
t.Run("ok, bind to map[string][]int skips with nil map", func(t *testing.T) {
var dest map[string][]int
assert.NoError(t, bindData(&dest, exampleData, "param", nil))
assert.Equal(t, map[string][]int(nil), dest)
})
}
func TestBindbindData(t *testing.T) {
ts := new(bindTestStruct)
err := bindData(ts, values, "form", nil)
assert.NoError(t, err)
assert.Equal(t, 0, ts.I)
assert.Equal(t, int8(0), ts.I8)
assert.Equal(t, int16(0), ts.I16)
assert.Equal(t, int32(0), ts.I32)
assert.Equal(t, int64(0), ts.I64)
assert.Equal(t, uint(0), ts.UI)
assert.Equal(t, uint8(0), ts.UI8)
assert.Equal(t, uint16(0), ts.UI16)
assert.Equal(t, uint32(0), ts.UI32)
assert.Equal(t, uint64(0), ts.UI64)
assert.Equal(t, false, ts.B)
assert.Equal(t, float32(0), ts.F32)
assert.Equal(t, float64(0), ts.F64)
assert.Equal(t, "", ts.S)
assert.Equal(t, "", ts.cantSet)
}
func TestBindParam(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.InitializeRoute(
&RouteInfo{Path: "/users/:id/:name"},
&PathValues{
{Name: "id", Value: "1"},
{Name: "name", Value: "Jon Snow"},
},
)
u := new(user)
err := c.Bind(u)
if assert.NoError(t, err) {
assert.Equal(t, 1, u.ID)
assert.Equal(t, "Jon Snow", u.Name)
}
// Second test for the absence of a param
c2 := e.NewContext(req, rec)
c2.InitializeRoute(
&RouteInfo{Path: "/users/:id"},
&PathValues{
{Name: "id", Value: "1"},
},
)
u = new(user)
err = c2.Bind(u)
if assert.NoError(t, err) {
assert.Equal(t, 1, u.ID)
assert.Equal(t, "", u.Name)
}
// Bind something with param and post data payload
body := bytes.NewBufferString(`{ "name": "Jon Snow" }`)
e2 := New()
req2 := httptest.NewRequest(http.MethodPost, "/", body)
req2.Header.Set(HeaderContentType, MIMEApplicationJSON)
rec2 := httptest.NewRecorder()
c3 := e2.NewContext(req2, rec2)
c3.InitializeRoute(
&RouteInfo{Path: "/users/:id"},
&PathValues{
{Name: "id", Value: "1"},
},
)
u = new(user)
err = c3.Bind(u)
if assert.NoError(t, err) {
assert.Equal(t, 1, u.ID)
assert.Equal(t, "Jon Snow", u.Name)
}
}
func TestBindUnmarshalTypeError(t *testing.T) {
body := bytes.NewBufferString(`{ "id": "text" }`)
e := New()
req := httptest.NewRequest(http.MethodPost, "/", body)
req.Header.Set(HeaderContentType, MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
u := new(user)
err := c.Bind(u)
assert.EqualError(t, err, `code=400, message=Bad Request, err=json: cannot unmarshal string into Go struct field user.id of type int`)
}
func TestBindSetWithProperType(t *testing.T) {
ts := new(bindTestStruct)
typ := reflect.TypeOf(ts).Elem()
val := reflect.ValueOf(ts).Elem()
for i := 0; i < typ.NumField(); i++ {
typeField := typ.Field(i)
structField := val.Field(i)
if !structField.CanSet() {
continue
}
if len(values[typeField.Name]) == 0 {
continue
}
val := values[typeField.Name][0]
err := setWithProperType(typeField.Type.Kind(), val, structField)
assert.NoError(t, err)
}
assertBindTestStruct(t, ts)
type foo struct {
Bar bytes.Buffer
}
v := &foo{}
typ = reflect.TypeOf(v).Elem()
val = reflect.ValueOf(v).Elem()
assert.Error(t, setWithProperType(typ.Field(0).Type.Kind(), "5", val.Field(0)))
}
func BenchmarkBindbindDataWithTags(b *testing.B) {
b.ReportAllocs()
ts := new(bindTestStructWithTags)
var err error
b.ResetTimer()
for i := 0; i < b.N; i++ {
err = bindData(ts, values, "form", nil)
}
assert.NoError(b, err)
assertBindTestStruct(b, (*bindTestStruct)(ts))
}
func assertBindTestStruct(tb testing.TB, ts *bindTestStruct) {
assert.Equal(tb, 0, ts.I)
assert.Equal(tb, int8(8), ts.I8)
assert.Equal(tb, int16(16), ts.I16)
assert.Equal(tb, int32(32), ts.I32)
assert.Equal(tb, int64(64), ts.I64)
assert.Equal(tb, uint(0), ts.UI)
assert.Equal(tb, uint8(8), ts.UI8)
assert.Equal(tb, uint16(16), ts.UI16)
assert.Equal(tb, uint32(32), ts.UI32)
assert.Equal(tb, uint64(64), ts.UI64)
assert.Equal(tb, true, ts.B)
assert.Equal(tb, float32(32.5), ts.F32)
assert.Equal(tb, float64(64.5), ts.F64)
assert.Equal(tb, "test", ts.S)
assert.Equal(tb, "", ts.GetCantSet())
}
func testBindOkay(t *testing.T, r io.Reader, query url.Values, ctype string) {
e := New()
path := "/"
if len(query) > 0 {
path += "?" + query.Encode()
}
req := httptest.NewRequest(http.MethodPost, path, r)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
req.Header.Set(HeaderContentType, ctype)
u := new(user)
err := c.Bind(u)
if assert.Equal(t, nil, err) {
assert.Equal(t, 1, u.ID)
assert.Equal(t, "Jon Snow", u.Name)
}
}
func testBindArrayOkay(t *testing.T, r io.Reader, query url.Values, ctype string) {
e := New()
path := "/"
if len(query) > 0 {
path += "?" + query.Encode()
}
req := httptest.NewRequest(http.MethodPost, path, r)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
req.Header.Set(HeaderContentType, ctype)
u := []user{}
err := c.Bind(&u)
if assert.NoError(t, err) {
assert.Equal(t, 1, len(u))
assert.Equal(t, 1, u[0].ID)
assert.Equal(t, "Jon Snow", u[0].Name)
}
}
func testBindError(t *testing.T, r io.Reader, ctype string, expectedInternal error) {
e := New()
req := httptest.NewRequest(http.MethodPost, "/", r)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
req.Header.Set(HeaderContentType, ctype)
u := new(user)
err := c.Bind(u)
switch {
case strings.HasPrefix(ctype, MIMEApplicationJSON), strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML),
strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
if assert.IsType(t, new(HTTPError), err) {
assert.Equal(t, http.StatusBadRequest, err.(*HTTPError).Code)
assert.IsType(t, expectedInternal, err.(*HTTPError).Unwrap())
}
default:
if assert.IsType(t, new(HTTPError), err) {
assert.Equal(t, ErrUnsupportedMediaType, err)
assert.IsType(t, expectedInternal, err.(*HTTPError).Unwrap())
}
}
}
func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
// tests to check binding behaviour when multiple sources (path params, query params and request body) are in use
// binding is done in steps and one source could overwrite previous source bound data
// these tests are to document this behaviour and detect further possible regressions when bind implementation is changed
type Opts struct {
Node string `json:"node" form:"node" query:"node" param:"node"`
Lang string
ID int `json:"id" form:"id" query:"id"`
}
var testCases = []struct {
givenContent io.Reader
whenBindTarget any
expect any
name string
givenURL string
givenMethod string
expectError string
whenNoPathValues bool
}{
{
name: "ok, POST bind to struct with: path param + query param + body",
givenMethod: http.MethodPost,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`{"id": 1}`),
expect: &Opts{ID: 1, Node: "node_from_path"}, // query params are not used, node is filled from path
},
{
name: "ok, PUT bind to struct with: path param + query param + body",
givenMethod: http.MethodPut,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`{"id": 1}`),
expect: &Opts{ID: 1, Node: "node_from_path"}, // query params are not used
},
{
name: "ok, GET bind to struct with: path param + query param + body",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`{"id": 1}`),
expect: &Opts{ID: 1, Node: "xxx"}, // query overwrites previous path value
},
{
name: "ok, GET bind to struct with: path param + query param + body",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
expect: &Opts{ID: 1, Node: "zzz"}, // body is bound last and overwrites previous (path,query) values
},
{
name: "ok, DELETE bind to struct with: path param + query param + body",
givenMethod: http.MethodDelete,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
expect: &Opts{ID: 1, Node: "zzz"}, // for DELETE body is bound after query params
},
{
name: "ok, POST bind to struct with: path param + body",
givenMethod: http.MethodPost,
givenURL: "/api/real_node/endpoint",
givenContent: strings.NewReader(`{"id": 1}`),
expect: &Opts{ID: 1, Node: "node_from_path"},
},
{
name: "ok, POST bind to struct with path + query + body = body has priority",
givenMethod: http.MethodPost,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
expect: &Opts{ID: 1, Node: "zzz"}, // field value from content has higher priority
},
{
name: "nok, POST body bind failure",
givenMethod: http.MethodPost,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`{`),
expect: &Opts{ID: 0, Node: "node_from_path"}, // query binding has already modified bind target
expectError: "code=400, message=Bad Request, err=unexpected EOF",
},
{
name: "nok, GET with body bind failure when types are not convertible",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint?id=nope",
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
expect: &Opts{ID: 0, Node: "node_from_path"}, // path params binding has already modified bind target
expectError: `code=400, message=Bad Request, err=strconv.ParseInt: parsing "nope": invalid syntax`,
},
{
name: "nok, GET body bind failure - trying to bind json array to struct",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`[{"id": 1}]`),
expect: &Opts{ID: 0, Node: "xxx"}, // query binding has already modified bind target
expectError: `code=400, message=Bad Request, err=json: cannot unmarshal array into Go value of type echo.Opts`,
},
{ // query param is ignored as we do not know where exactly to bind it in slice
name: "ok, GET bind to struct slice, ignore query param",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`[{"id": 1}]`),
whenNoPathValues: true,
whenBindTarget: &[]Opts{},
expect: &[]Opts{
{ID: 1, Node: ""},
},
},
{ // binding query params interferes with body. b.BindBody() should be used to bind only body to slice
name: "ok, POST binding to slice should not be affected query params types",
givenMethod: http.MethodPost,
givenURL: "/api/real_node/endpoint?id=nope&node=xxx",
givenContent: strings.NewReader(`[{"id": 1}]`),
whenNoPathValues: true,
whenBindTarget: &[]Opts{},
expect: &[]Opts{{ID: 1}},
expectError: "",
},
{ // path param is ignored as we do not know where exactly to bind it in slice
name: "ok, GET bind to struct slice, ignore path param",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`[{"id": 1}]`),
whenBindTarget: &[]Opts{},
expect: &[]Opts{
{ID: 1, Node: ""},
},
},
{
name: "ok, GET body bind json array to slice",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint",
givenContent: strings.NewReader(`[{"id": 1}]`),
whenNoPathValues: true,
whenBindTarget: &[]Opts{},
expect: &[]Opts{{ID: 1, Node: ""}},
expectError: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
e := New()
// assume route we are testing is "/api/:node/endpoint?some_query_params=here"
req := httptest.NewRequest(tc.givenMethod, tc.givenURL, tc.givenContent)
req.Header.Set(HeaderContentType, MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
if !tc.whenNoPathValues {
c.SetPathValues(PathValues{
{Name: "node", Value: "node_from_path"},
})
}
var bindTarget any
if tc.whenBindTarget != nil {
bindTarget = tc.whenBindTarget
} else {
bindTarget = &Opts{}
}
b := new(DefaultBinder)
err := b.Bind(c, bindTarget)
if tc.expectError != "" {
assert.EqualError(t, err, tc.expectError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.expect, bindTarget)
})
}
}
func TestDefaultBinder_BindBody(t *testing.T) {
// tests to check binding behaviour when multiple sources (path params, query params and request body) are in use
// generally when binding from request body - URL and path params are ignored - unless form is being bound.
// these tests are to document this behaviour and detect further possible regressions when bind implementation is changed
type Node struct {
Node string `json:"node" xml:"node" form:"node" query:"node" param:"node"`
ID int `json:"id" xml:"id" form:"id" query:"id"`
}
type Nodes struct {
Nodes []Node `xml:"node" form:"node"`
}
var testCases = []struct {
givenContent io.Reader
whenBindTarget any
expect any
name string
givenURL string
givenMethod string
givenContentType string
expectError string
whenNoPathValues bool
whenChunkedBody bool
}{
{
name: "ok, JSON POST bind to struct with: path + query + empty field in body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationJSON,
givenContent: strings.NewReader(`{"id": 1}`),
expect: &Node{ID: 1, Node: ""}, // path params or query params should not interfere with body
},
{
name: "ok, JSON POST bind to struct with: path + query + body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationJSON,
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
expect: &Node{ID: 1, Node: "zzz"}, // field value from content has higher priority
},
{
name: "ok, JSON POST body bind json array to slice (has matching path/query params)",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationJSON,
givenContent: strings.NewReader(`[{"id": 1}]`),
whenNoPathValues: true,
whenBindTarget: &[]Node{},
expect: &[]Node{{ID: 1, Node: ""}},
expectError: "",
},
{ // rare case as GET is not usually used to send request body
name: "ok, JSON GET bind to struct with: path + query + empty field in body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodGet,
givenContentType: MIMEApplicationJSON,
givenContent: strings.NewReader(`{"id": 1}`),
expect: &Node{ID: 1, Node: ""}, // path params or query params should not interfere with body
},
{ // rare case as GET is not usually used to send request body
name: "ok, JSON GET bind to struct with: path + query + body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodGet,
givenContentType: MIMEApplicationJSON,
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
expect: &Node{ID: 1, Node: "zzz"}, // field value from content has higher priority
},
{
name: "nok, JSON POST body bind failure",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationJSON,
givenContent: strings.NewReader(`{`),
expect: &Node{ID: 0, Node: ""},
expectError: "code=400, message=Bad Request, err=unexpected EOF",
},
{
name: "ok, XML POST bind to struct with: path + query + empty body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationXML,
givenContent: strings.NewReader(`<node><id>1</id><node>yyy</node></node>`),
expect: &Node{ID: 1, Node: "yyy"},
},
{
name: "ok, XML POST bind array to slice with: path + query + body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationXML,
givenContent: strings.NewReader(`<nodes><node><id>1</id><node>yyy</node></node></nodes>`),
whenBindTarget: &Nodes{},
expect: &Nodes{Nodes: []Node{{ID: 1, Node: "yyy"}}},
},
{
name: "nok, XML POST bind failure",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationXML,
givenContent: strings.NewReader(`<node><`),
expect: &Node{ID: 0, Node: ""},
expectError: "code=400, message=Bad Request, err=XML syntax error on line 1: unexpected EOF",
},
{
name: "ok, FORM POST bind to struct with: path + query + body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationForm,
givenContent: strings.NewReader(`id=1&node=yyy`),
expect: &Node{ID: 1, Node: "yyy"},
},
{
// NB: form values are taken from BOTH body and query for POST/PUT/PATCH by standard library implementation
// See: https://golang.org/pkg/net/http/#Request.ParseForm
name: "ok, FORM POST bind to struct with: path + query + empty field in body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationForm,
givenContent: strings.NewReader(`id=1`),
expect: &Node{ID: 1, Node: "xxx"},
},
{
// NB: form values are taken from query by standard library implementation
// See: https://golang.org/pkg/net/http/#Request.ParseForm
name: "ok, FORM GET bind to struct with: path + query + empty field in body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodGet,
givenContentType: MIMEApplicationForm,
givenContent: strings.NewReader(`id=1`),
expect: &Node{ID: 0, Node: "xxx"}, // 'xxx' is taken from URL and body is not used with GET by implementation
},
{
name: "nok, unsupported content type",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMETextPlain,
givenContent: strings.NewReader(`<html></html>`),
expect: &Node{ID: 0, Node: ""},
expectError: "code=415, message=Unsupported Media Type",
},
// FIXME: REASON in Go 1.24 and earlier http.NoBody would result ContentLength=-1
// but as of Go 1.25 http.NoBody would result ContentLength=0
// I am too lazy to bother documenting this as 2 version specific tests.
//{
// name: "nok, JSON POST with http.NoBody",
// givenURL: "/api/real_node/endpoint?node=xxx",
// givenMethod: http.MethodPost,
// givenContentType: MIMEApplicationJSON,
// givenContent: http.NoBody,
// expect: &Node{ID: 0, Node: ""},
// expectError: "code=400, message=EOF, internal=EOF",
//},
{
name: "ok, JSON POST with empty body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationJSON,
givenContent: strings.NewReader(""),
expect: &Node{ID: 0, Node: ""},
},
{
name: "ok, JSON POST bind to struct with: path + query + chunked body",
givenURL: "/api/real_node/endpoint?node=xxx",
givenMethod: http.MethodPost,
givenContentType: MIMEApplicationJSON,
givenContent: httputil.NewChunkedReader(strings.NewReader("18\r\n" + `{"id": 1, "node": "zzz"}` + "\r\n0\r\n")),
whenChunkedBody: true,
expect: &Node{ID: 1, Node: "zzz"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
e := New()
// assume route we are testing is "/api/:node/endpoint?some_query_params=here"
req := httptest.NewRequest(tc.givenMethod, tc.givenURL, tc.givenContent)
switch tc.givenContentType {
case MIMEApplicationXML:
req.Header.Set(HeaderContentType, MIMEApplicationXML)
case MIMEApplicationForm:
req.Header.Set(HeaderContentType, MIMEApplicationForm)
case MIMEApplicationJSON:
req.Header.Set(HeaderContentType, MIMEApplicationJSON)
}
if tc.whenChunkedBody {
req.ContentLength = -1
req.TransferEncoding = append(req.TransferEncoding, "chunked")
}
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
if !tc.whenNoPathValues {
c.SetPathValues(PathValues{
{Name: "node", Value: "real_node"},
})
}
var bindTarget any
if tc.whenBindTarget != nil {
bindTarget = tc.whenBindTarget
} else {
bindTarget = &Node{}
}
err := BindBody(c, bindTarget)
if tc.expectError != "" {
assert.EqualError(t, err, tc.expectError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.expect, bindTarget)
})
}
}
func testBindURL(queryString string, target any) error {
e := New()
req := httptest.NewRequest(http.MethodGet, queryString, nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
return c.Bind(target)
}
type unixTimestamp struct {
Time time.Time
}
func (t *unixTimestamp) UnmarshalParam(param string) error {
n, err := strconv.ParseInt(param, 10, 64)
if err != nil {
return fmt.Errorf("'%s' is not an integer", param)
}
*t = unixTimestamp{Time: time.Unix(n, 0)}
return err
}
type IntArrayA []int
// UnmarshalParam converts value to *Int64Slice. This allows the API to accept
// a comma-separated list of integers as a query parameter.
func (i *IntArrayA) UnmarshalParam(value string) error {
var values = strings.Split(value, ",")
var numbers = make([]int, 0, len(values))
for _, v := range values {
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return fmt.Errorf("'%s' is not an integer", v)
}
numbers = append(numbers, int(n))
}
*i = append(*i, numbers...)
return nil
}
func TestBindUnmarshalParamExtras(t *testing.T) {
// this test documents how bind handles `BindUnmarshaler` interface:
// NOTE: BindUnmarshaler chooses first input value to be bound.
t.Run("nok, unmarshalling fails", func(t *testing.T) {
result := struct {
V unixTimestamp `query:"t"`
}{}
err := testBindURL("/?t=xxxx", &result)
assert.EqualError(t, err, `code=400, message=Bad Request, err='xxxx' is not an integer`)
})
t.Run("ok, target is struct", func(t *testing.T) {
result := struct {
V unixTimestamp `query:"t"`
}{}
err := testBindURL("/?t=1710095540&t=1710095541", &result)
assert.NoError(t, err)
expect := unixTimestamp{
Time: time.Unix(1710095540, 0),
}
assert.Equal(t, expect, result.V)
})
t.Run("ok, target is an alias to slice and is nil, append only values from first", func(t *testing.T) {
result := struct {
V IntArrayA `query:"a"`
}{}
err := testBindURL("/?a=1,2,3&a=4,5,6", &result)
assert.NoError(t, err)
assert.Equal(t, IntArrayA([]int{1, 2, 3}), result.V)
})
t.Run("ok, target is an alias to slice and is nil, single input", func(t *testing.T) {
result := struct {
V IntArrayA `query:"a"`
}{}
err := testBindURL("/?a=1,2", &result)
assert.NoError(t, err)
assert.Equal(t, IntArrayA([]int{1, 2}), result.V)
})
t.Run("ok, target is pointer an alias to slice and is nil", func(t *testing.T) {
result := struct {
V *IntArrayA `query:"a"`
}{}
err := testBindURL("/?a=1&a=4,5,6", &result)
assert.NoError(t, err)
var expected = IntArrayA([]int{1})
assert.Equal(t, &expected, result.V)
})
t.Run("ok, target is pointer an alias to slice and is NOT nil", func(t *testing.T) {
result := struct {
V *IntArrayA `query:"a"`
}{}
result.V = new(IntArrayA) // NOT nil
err := testBindURL("/?a=1&a=4,5,6", &result)
assert.NoError(t, err)
var expected = IntArrayA([]int{1})
assert.Equal(t, &expected, result.V)
})
}
type unixTimestampLast struct {
Time time.Time
}
// this is silly example for `bindMultipleUnmarshaler` for type that uses last input value for unmarshalling
func (t *unixTimestampLast) UnmarshalParams(params []string) error {
lastInput := params[len(params)-1]
n, err := strconv.ParseInt(lastInput, 10, 64)
if err != nil {
return fmt.Errorf("'%s' is not an integer", lastInput)
}
*t = unixTimestampLast{Time: time.Unix(n, 0)}
return err
}
type IntArrayB []int
func (i *IntArrayB) UnmarshalParams(params []string) error {
var numbers = make([]int, 0, len(params))
for _, param := range params {
var values = strings.Split(param, ",")
for _, v := range values {
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return fmt.Errorf("'%s' is not an integer", v)
}
numbers = append(numbers, int(n))
}
}
*i = append(*i, numbers...)
return nil
}
func TestBindUnmarshalParams(t *testing.T) {
// this test documents how bind handles `bindMultipleUnmarshaler` interface:
t.Run("nok, unmarshalling fails", func(t *testing.T) {
result := struct {
V unixTimestampLast `query:"t"`
}{}
err := testBindURL("/?t=xxxx", &result)
assert.EqualError(t, err, "code=400, message=Bad Request, err='xxxx' is not an integer")
})
t.Run("ok, target is struct", func(t *testing.T) {
result := struct {
V unixTimestampLast `query:"t"`
}{}
err := testBindURL("/?t=1710095540&t=1710095541", &result)
assert.NoError(t, err)
expect := unixTimestampLast{
Time: time.Unix(1710095541, 0),
}
assert.Equal(t, expect, result.V)
})
t.Run("ok, target is an alias to slice and is nil, append multiple inputs", func(t *testing.T) {
result := struct {
V IntArrayB `query:"a"`
}{}
err := testBindURL("/?a=1,2,3&a=4,5,6", &result)
assert.NoError(t, err)
assert.Equal(t, IntArrayB([]int{1, 2, 3, 4, 5, 6}), result.V)
})
t.Run("ok, target is an alias to slice and is nil, single input", func(t *testing.T) {
result := struct {
V IntArrayB `query:"a"`
}{}
err := testBindURL("/?a=1,2", &result)
assert.NoError(t, err)
assert.Equal(t, IntArrayB([]int{1, 2}), result.V)
})
t.Run("ok, target is pointer an alias to slice and is nil", func(t *testing.T) {
result := struct {
V *IntArrayB `query:"a"`
}{}
err := testBindURL("/?a=1&a=4,5,6", &result)
assert.NoError(t, err)
var expected = IntArrayB([]int{1, 4, 5, 6})
assert.Equal(t, &expected, result.V)
})
t.Run("ok, target is pointer an alias to slice and is NOT nil", func(t *testing.T) {
result := struct {
V *IntArrayB `query:"a"`
}{}
result.V = new(IntArrayB) // NOT nil
err := testBindURL("/?a=1&a=4,5,6", &result)
assert.NoError(t, err)
var expected = IntArrayB([]int{1, 4, 5, 6})
assert.Equal(t, &expected, result.V)
})
}
func TestBindInt8(t *testing.T) {
t.Run("nok, binding fails", func(t *testing.T) {
type target struct {
V int8 `query:"v"`
}
p := target{}
err := testBindURL("/?v=x&v=2", &p)
assert.EqualError(t, err, `code=400, message=Bad Request, err=strconv.ParseInt: parsing "x": invalid syntax`)
})
t.Run("nok, int8 embedded in struct", func(t *testing.T) {
type target struct {
int8 `query:"v"` // embedded field is `Anonymous`. We can only set public fields
}
p := target{}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{0}, p)
})
t.Run("nok, pointer to int8 embedded in struct", func(t *testing.T) {
type target struct {
*int8 `query:"v"` // embedded field is `Anonymous`. We can only set public fields
}
p := target{}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{int8: nil}, p)
})
t.Run("ok, bind int8 as struct field", func(t *testing.T) {
type target struct {
V int8 `query:"v"`
}
p := target{V: 127}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{V: 1}, p)
})
t.Run("ok, bind pointer to int8 as struct field, value is nil", func(t *testing.T) {
type target struct {
V *int8 `query:"v"`
}
p := target{}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{V: ptr(int8(1))}, p)
})
t.Run("ok, bind pointer to int8 as struct field, value is set", func(t *testing.T) {
type target struct {
V *int8 `query:"v"`
}
p := target{V: ptr(int8(127))}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{V: ptr(int8(1))}, p)
})
t.Run("ok, bind int8 slice as struct field, value is nil", func(t *testing.T) {
type target struct {
V []int8 `query:"v"`
}
p := target{}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{V: []int8{1, 2}}, p)
})
t.Run("ok, bind slice of int8 as struct field, value is set", func(t *testing.T) {
type target struct {
V []int8 `query:"v"`
}
p := target{V: []int8{111}}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{V: []int8{1, 2}}, p)
})
t.Run("ok, bind slice of pointer to int8 as struct field, value is set", func(t *testing.T) {
type target struct {
V []*int8 `query:"v"`
}
p := target{V: []*int8{ptr(int8(127))}}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{V: []*int8{ptr(int8(1)), ptr(int8(2))}}, p)
})
t.Run("ok, bind pointer to slice of int8 as struct field, value is set", func(t *testing.T) {
type target struct {
V *[]int8 `query:"v"`
}
p := target{V: &[]int8{111}}
err := testBindURL("/?v=1&v=2", &p)
assert.NoError(t, err)
assert.Equal(t, target{V: &[]int8{1, 2}}, p)
})
}
func TestBindMultipartFormFiles(t *testing.T) {
file1 := createTestFormFile("file", "file1.txt")
file11 := createTestFormFile("file", "file11.txt")
file2 := createTestFormFile("file2", "file2.txt")
filesA := createTestFormFile("files", "filesA.txt")
filesB := createTestFormFile("files", "filesB.txt")
t.Run("nok, can not bind to multipart file struct", func(t *testing.T) {
var target struct {
File multipart.FileHeader `form:"file"`
}
err := bindMultipartFiles(t, &target, file1, file2) // file2 should be ignored
assert.EqualError(t, err, `code=400, message=Bad Request, err=binding to multipart.FileHeader struct is not supported, use pointer to struct`)
})
t.Run("ok, bind single multipart file to pointer to multipart file", func(t *testing.T) {
var target struct {
File *multipart.FileHeader `form:"file"`
}
err := bindMultipartFiles(t, &target, file1, file2) // file2 should be ignored
assert.NoError(t, err)
assertMultipartFileHeader(t, target.File, file1)
})
t.Run("ok, bind multiple multipart files to pointer to multipart file", func(t *testing.T) {
var target struct {
File *multipart.FileHeader `form:"file"`
}
err := bindMultipartFiles(t, &target, file1, file11)
assert.NoError(t, err)
assertMultipartFileHeader(t, target.File, file1) // should choose first one
})
t.Run("ok, bind multiple multipart files to slice of multipart file", func(t *testing.T) {
var target struct {
Files []multipart.FileHeader `form:"files"`
}
err := bindMultipartFiles(t, &target, filesA, filesB, file1)
assert.NoError(t, err)
assert.Len(t, target.Files, 2)
assertMultipartFileHeader(t, &target.Files[0], filesA)
assertMultipartFileHeader(t, &target.Files[1], filesB)
})
t.Run("ok, bind multiple multipart files to slice of pointer to multipart file", func(t *testing.T) {
var target struct {
Files []*multipart.FileHeader `form:"files"`
}
err := bindMultipartFiles(t, &target, filesA, filesB, file1)
assert.NoError(t, err)
assert.Len(t, target.Files, 2)
assertMultipartFileHeader(t, target.Files[0], filesA)
assertMultipartFileHeader(t, target.Files[1], filesB)
})
}
type testFormFile struct {
Fieldname string
Filename string
Content []byte
}
func createTestFormFile(formFieldName string, filename string) testFormFile {
return testFormFile{
Fieldname: formFieldName,
Filename: filename,
Content: []byte(strings.Repeat(filename, 10)),
}
}
func bindMultipartFiles(t *testing.T, target any, files ...testFormFile) error {
var body bytes.Buffer
mw := multipart.NewWriter(&body)
for _, file := range files {
fw, err := mw.CreateFormFile(file.Fieldname, file.Filename)
assert.NoError(t, err)
n, err := fw.Write(file.Content)
assert.NoError(t, err)
assert.Equal(t, len(file.Content), n)
}
err := mw.Close()
assert.NoError(t, err)
req, err := http.NewRequest(http.MethodPost, "/", &body)
assert.NoError(t, err)
req.Header.Set("Content-Type", mw.FormDataContentType())
rec := httptest.NewRecorder()
e := New()
c := e.NewContext(req, rec)
return c.Bind(target)
}
func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFormFile) {
assert.Equal(t, file.Filename, fh.Filename)
assert.Equal(t, int64(len(file.Content)), fh.Size)
fl, err := fh.Open()
assert.NoError(t, err)
body, err := io.ReadAll(fl)
assert.NoError(t, err)
assert.Equal(t, string(file.Content), string(body))
err = fl.Close()
assert.NoError(t, err)
}
func TestTimeFormatBinding(t *testing.T) {
type TestStruct struct {
DateTimeLocal time.Time `form:"datetime_local" format:"2006-01-02T15:04"`
Date time.Time `query:"date" format:"2006-01-02"`
CustomFormat time.Time `form:"custom" format:"01/02/2006 15:04:05"`
DefaultTime time.Time `form:"default_time"` // No format tag - should use default parsing
PtrTime *time.Time `query:"ptr_time" format:"2006-01-02"`
}
testCases := []struct {
name string
contentType string
data string
queryParams string
expect TestStruct
expectError bool
}{
{
name: "ok, datetime-local format binding",
contentType: MIMEApplicationForm,
data: "datetime_local=2023-12-25T14:30&default_time=2023-12-25T14:30:45Z",
expect: TestStruct{
DateTimeLocal: time.Date(2023, 12, 25, 14, 30, 0, 0, time.UTC),
DefaultTime: time.Date(2023, 12, 25, 14, 30, 45, 0, time.UTC),
},
},
{
name: "ok, date format binding via query params",
queryParams: "?date=2023-01-15&ptr_time=2023-02-20",
expect: TestStruct{
Date: time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
PtrTime: &time.Time{},
},
},
{
name: "ok, custom format via form data",
contentType: MIMEApplicationForm,
data: "custom=12/25/2023 14:30:45",
expect: TestStruct{
CustomFormat: time.Date(2023, 12, 25, 14, 30, 45, 0, time.UTC),
},
},
{
name: "nok, invalid format should fail",
contentType: MIMEApplicationForm,
data: "datetime_local=invalid-date",
expectError: true,
},
{
name: "nok, wrong format should fail",
contentType: MIMEApplicationForm,
data: "datetime_local=2023-12-25", // Missing time part
expectError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
e := New()
var req *http.Request
if tc.contentType == MIMEApplicationJSON {
req = httptest.NewRequest(http.MethodPost, "/"+tc.queryParams, strings.NewReader(tc.data))
req.Header.Set(HeaderContentType, tc.contentType)
} else if tc.contentType == MIMEApplicationForm {
req = httptest.NewRequest(http.MethodPost, "/"+tc.queryParams, strings.NewReader(tc.data))
req.Header.Set(HeaderContentType, tc.contentType)
} else {
req = httptest.NewRequest(http.MethodGet, "/"+tc.queryParams, nil)
}
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
var result TestStruct
err := c.Bind(&result)
if tc.expectError {
assert.Error(t, err)
return
}
assert.NoError(t, err)
// Check individual fields since time comparison can be tricky
if !tc.expect.DateTimeLocal.IsZero() {
assert.True(t, tc.expect.DateTimeLocal.Equal(result.DateTimeLocal),
"DateTimeLocal: expected %v, got %v", tc.expect.DateTimeLocal, result.DateTimeLocal)
}
if !tc.expect.Date.IsZero() {
assert.True(t, tc.expect.Date.Equal(result.Date),
"Date: expected %v, got %v", tc.expect.Date, result.Date)
}
if !tc.expect.CustomFormat.IsZero() {
assert.True(t, tc.expect.CustomFormat.Equal(result.CustomFormat),
"CustomFormat: expected %v, got %v", tc.expect.CustomFormat, result.CustomFormat)
}
if !tc.expect.DefaultTime.IsZero() {
assert.True(t, tc.expect.DefaultTime.Equal(result.DefaultTime),
"DefaultTime: expected %v, got %v", tc.expect.DefaultTime, result.DefaultTime)
}
if tc.expect.PtrTime != nil {
assert.NotNil(t, result.PtrTime)
if result.PtrTime != nil {
expectedPtr := time.Date(2023, 2, 20, 0, 0, 0, 0, time.UTC)
assert.True(t, expectedPtr.Equal(*result.PtrTime),
"PtrTime: expected %v, got %v", expectedPtr, *result.PtrTime)
}
}
})
}
}
================================================
FILE: binder.go
================================================
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"encoding"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
/**
Following functions provide handful of methods for binding to Go native types from request query or path parameters.
* QueryParamsBinder(c) - binds query parameters (source URL)
* PathValuesBinder(c) - binds path parameters (source URL)
* FormFieldBinder(c) - binds form fields (source URL + body)
Example:
```go
var length int64
err := echo.QueryParamsBinder(c).Int64("length", &length).BindError()
```
For every supported type there are following methods:
* <Type>("param", &destination) - if parameter value exists then binds it to given destination of that type i.e Int64(...).
* Must<Type>("param", &destination) - parameter value is required to exist, binds it to given destination of that type i.e MustInt64(...).
* <Type>s("param", &destination) - (for slices) if parameter values exists then binds it to given destination of that type i.e Int64s(...).
* Must<Type>s("param", &destination) - (for slices) parameter value is required to exist, binds it to given destination of that type i.e MustInt64s(...).
for some slice types `BindWithDelimiter("param", &dest, ",")` supports splitting parameter values before type conversion is done
i.e. URL `/api/search?id=1,2,3&id=1` can be bind to `[]int64{1,2,3,1}`
`FailFast` flags binder to stop binding after first bind error during binder call chain. Enabled by default.
`BindError()` returns first bind error from binder and resets errors in binder. Useful along with `FailFast()` method
to do binding and returns on first problem
`BindErrors()` returns all bind errors from binder and resets errors in binder.
Types that are supported:
* bool
* float32
* float64
* int
* int8
* int16
* int32
* int64
* uint
* uint8/byte (does not support `bytes()`. Use BindUnmarshaler/CustomFunc to convert value from base64 etc to []byte{})
* uint16
* uint32
* uint64
* string
* time
* duration
* BindUnmarshaler() interface
* TextUnmarshaler() interface
* JSONUnmarshaler() interface
* UnixTime() - converts unix time (integer) to time.Time
* UnixTimeMilli() - converts unix time with millisecond precision (integer) to time.Time
* UnixTimeNano() - converts unix time with nanosecond precision (integer) to time.Time
* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
*/
// BindingError represents an error that occurred while binding request data.
type BindingError struct {
// Field is the field name where value binding failed
Field string `json:"field"`
*HTTPError
// Values of parameter that failed to bind.
Values []string `json:"-"`
}
// NewBindingError creates new instance of binding error
func NewBindingError(sourceParam string, values []string, message string, err error) error {
return &BindingError{
Field: sourceParam,
Values: values,
HTTPError: &HTTPError{Code: http.StatusBadRequest, Message: message, err: err},
}
}
// Error returns error message
func (be *BindingError) Error() string {
return fmt.Sprintf("%s, field=%s", be.HTTPError.Error(), be.Field)
}
// ValueBinder provides utility methods for binding query or path parameter to various Go built-in types
type ValueBinder struct {
// ValueFunc is used to get single parameter (first) value from request
ValueFunc func(sourceParam string) string
// ValuesFunc is used to get all values for parameter from request. i.e. `/api/search?ids=1&ids=2`
ValuesFunc func(sourceParam string) []string
// ErrorFunc is used to create errors. Allows you to use your own error type, that for example marshals to your specific json response
ErrorFunc func(sourceParam string, values []string, message string, internalError error) error
errors []error
// failFast is flag for binding methods to return without attempting to bind when previous binding already failed
failFast bool
}
// QueryParamsBinder creates query parameter value binder
func QueryParamsBinder(c *Context) *ValueBinder {
return &ValueBinder{
failFast: true,
ValueFunc: c.QueryParam,
ValuesFunc: func(sourceParam string) []string {
values, ok := c.QueryParams()[sourceParam]
if !ok {
return nil
}
return values
},
ErrorFunc: NewBindingError,
}
}
// PathValuesBinder creates path parameter value binder
func PathValuesBinder(c *Context) *ValueBinder {
return &ValueBinder{
failFast: true,
ValueFunc: c.Param,
ValuesFunc: func(sourceParam string) []string {
// path parameter should not have multiple values so getting values does not make sense but lets not error out here
value := c.Param(sourceParam)
if value == "" {
return nil
}
return []string{value}
},
ErrorFunc: NewBindingError,
}
}
// FormFieldBinder creates form field value binder
// For all requests, FormFieldBinder parses the raw query from the URL and uses query params as form fields
//
// For POST, PUT, and PATCH requests, it also reads the request body, parses it
// as a form and uses query params as form fields. Request body parameters take precedence over URL query
// string values in r.Form.
//
// NB: when binding forms take note that this implementation uses standard library form parsing
// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm
// See https://golang.org/pkg/net/http/#Request.ParseForm
func FormFieldBinder(c *Context) *ValueBinder {
vb := &ValueBinder{
failFast: true,
ValueFunc: func(sourceParam string) string {
return c.Request().FormValue(sourceParam)
},
ErrorFunc: NewBindingError,
}
vb.ValuesFunc = func(sourceParam string) []string {
if c.Request().Form == nil {
// this is same as `Request().FormValue()` does internally
_, _ = c.MultipartForm() // we want to trigger c.request.ParseMultipartForm(c.formParseMaxMemory)
}
values, ok := c.Request().Form[sourceParam]
if !ok {
return nil
}
return values
}
return vb
}
// FailFast set internal flag to indicate if binding methods will return early (without binding) when previous bind failed
// NB: call this method before any other binding methods as it modifies binding methods behaviour
func (b *ValueBinder) FailFast(value bool) *ValueBinder {
b.failFast = value
return b
}
func (b *ValueBinder) setError(err error) {
if b.errors == nil {
b.errors = []error{err}
return
}
b.errors = append(b.errors, err)
}
// BindError returns first seen bind error and resets/empties binder errors for further calls
func (b *ValueBinder) BindError() error {
if b.errors == nil {
return nil
}
err := b.errors[0]
b.errors = nil // reset errors so next chain will start from zero
return err
}
// BindErrors returns all bind errors and resets/empties binder errors for further calls
func (b *ValueBinder) BindErrors() []error {
if b.errors == nil {
return nil
}
errors := b.errors
b.errors = nil // reset errors so next chain will start from zero
return errors
}
// CustomFunc binds parameter values with Func. Func is called only when parameter values exist.
func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder {
return b.customFunc(sourceParam, customFunc, false)
}
// MustCustomFunc requires parameter values to exist to bind with Func. Returns error when value does not exist.
func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder {
return b.customFunc(sourceParam, customFunc, true)
}
func (b *ValueBinder) customFunc(sourceParam string, customFunc func(values []string) []error, valueMustExist bool) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
values := b.ValuesFunc(sourceParam)
if len(values) == 0 {
if valueMustExist {
b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
}
return b
}
if errs := customFunc(values); errs != nil {
b.errors = append(b.errors, errs...)
}
return b
}
// String binds parameter to string variable
func (b *ValueBinder) String(sourceParam string, dest *string) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
value := b.ValueFunc(sourceParam)
if value == "" {
return b
}
*dest = value
return b
}
// MustString requires parameter value to exist to bind to string variable. Returns error when value does not exist
func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
value := b.ValueFunc(sourceParam)
if value == "" {
b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil))
return b
}
*dest = value
return b
}
// Strings binds parameter values to slice of string
func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
value := b.ValuesFunc(sourceParam)
if value == nil {
return b
}
*dest = value
return b
}
// MustStrings requires parameter values to exist to bind to slice of string variables. Returns error when value does not exist
func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
value := b.ValuesFunc(sourceParam)
if value == nil {
b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
return b
}
*dest = value
return b
}
// BindUnmarshaler binds parameter to destination implementing BindUnmarshaler interface
func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
return b
}
if err := dest.UnmarshalParam(tmp); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to BindUnmarshaler interface", err))
}
return b
}
// MustBindUnmarshaler requires parameter value to exist to bind to destination implementing BindUnmarshaler interface.
// Returns error when value does not exist
func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
value := b.ValueFunc(sourceParam)
if value == "" {
b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil))
return b
}
if err := dest.UnmarshalParam(value); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to BindUnmarshaler interface", err))
}
return b
}
// JSONUnmarshaler binds parameter to destination implementing json.Unmarshaler interface
func (b *ValueBinder) JSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
return b
}
if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
}
return b
}
// MustJSONUnmarshaler requires parameter value to exist to bind to destination implementing json.Unmarshaler interface.
// Returns error when value does not exist
func (b *ValueBinder) MustJSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
return b
}
if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
}
return b
}
// TextUnmarshaler binds parameter to destination implementing encoding.TextUnmarshaler interface
func (b *ValueBinder) TextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
return b
}
if err := dest.UnmarshalText([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
}
return b
}
// MustTextUnmarshaler requires parameter value to exist to bind to destination implementing encoding.TextUnmarshaler interface.
// Returns error when value does not exist
func (b *ValueBinder) MustTextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
return b
}
if err := dest.UnmarshalText([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
}
return b
}
// BindWithDelimiter binds parameter to destination by suitable conversion function.
// Delimiter is used before conversion to split parameter value to separate values
func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest any, delimiter string) *ValueBinder {
return b.bindWithDelimiter(sourceParam, dest, delimiter, false)
}
// MustBindWithDelimiter requires parameter value to exist to bind destination by suitable conversion function.
// Delimiter is used before conversion to split parameter value to separate values
func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest any, delimiter string) *ValueBinder {
return b.bindWithDelimiter(sourceParam, dest, delimiter, true)
}
func (b *ValueBinder) bindWithDelimiter(sourceParam string, dest any, delimiter string, valueMustExist bool) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
values := b.ValuesFunc(sourceParam)
if len(values) == 0 {
if valueMustExist {
b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
}
return b
}
tmpValues := make([]string, 0, len(values))
for _, v := range values {
tmpValues = append(tmpValues, strings.Split(v, delimiter)...)
}
switch d := dest.(type) {
case *[]string:
*d = tmpValues
return b
case *[]bool:
return b.bools(sourceParam, tmpValues, d)
case *[]int64, *[]int32, *[]int16, *[]int8, *[]int:
return b.ints(sourceParam, tmpValues, d)
case *[]uint64, *[]uint32, *[]uint16, *[]uint8, *[]uint: // *[]byte is same as *[]uint8
return b.uints(sourceParam, tmpValues, d)
case *[]float64, *[]float32:
return b.floats(sourceParam, tmpValues, d)
case *[]time.Duration:
return b.durations(sourceParam, tmpValues, d)
default:
// support only cases when destination is slice
// does not support time.Time as it needs argument (layout) for parsing or BindUnmarshaler
b.setError(b.ErrorFunc(sourceParam, []string{}, "unsupported bind type", nil))
return b
}
}
// Int64 binds parameter to int64 variable
func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBinder {
return b.intValue(sourceParam, dest, 64, false)
}
// MustInt64 requires parameter value to exist to bind to int64 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder {
return b.intValue(sourceParam, dest, 64, true)
}
// Int32 binds parameter to int32 variable
func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBinder {
return b.intValue(sourceParam, dest, 32, false)
}
// MustInt32 requires parameter value to exist to bind to int32 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder {
return b.intValue(sourceParam, dest, 32, true)
}
// Int16 binds parameter to int16 variable
func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBinder {
return b.intValue(sourceParam, dest, 16, false)
}
// MustInt16 requires parameter value to exist to bind to int16 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder {
return b.intValue(sourceParam, dest, 16, true)
}
// Int8 binds parameter to int8 variable
func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder {
return b.intValue(sourceParam, dest, 8, false)
}
// MustInt8 requires parameter value to exist to bind to int8 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder {
return b.intValue(sourceParam, dest, 8, true)
}
// Int binds parameter to int variable
func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder {
return b.intValue(sourceParam, dest, 0, false)
}
// MustInt requires parameter value to exist to bind to int variable. Returns error when value does not exist
func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder {
return b.intValue(sourceParam, dest, 0, true)
}
func (b *ValueBinder) intValue(sourceParam string, dest any, bitSize int, valueMustExist bool) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
value := b.ValueFunc(sourceParam)
if value == "" {
if valueMustExist {
b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
}
return b
}
return b.int(sourceParam, value, dest, bitSize)
}
func (b *ValueBinder) int(sourceParam string, value string, dest any, bitSize int) *ValueBinder {
n, err := strconv.ParseInt(value, 10, bitSize)
if err != nil {
if bitSize == 0 {
b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to int", err))
} else {
b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to int%v", bitSize), err))
}
return b
}
switch d := dest.(type) {
case *int64:
*d = n
case *int32:
*d = int32(n) // #nosec G115
case *int16:
*d = int16(n) // #nosec G115
case *int8:
*d = int8(n) // #nosec G115
case *int:
*d = int(n)
}
return b
}
func (b *ValueBinder) intsValue(sourceParam string, dest any, valueMustExist bool) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
values := b.ValuesFunc(sourceParam)
if len(values) == 0 {
if valueMustExist {
b.setError(b.ErrorFunc(sourceParam, values, "required field value is empty", nil))
}
return b
}
return b.ints(sourceParam, values, dest)
}
func (b *ValueBinder) ints(sourceParam string, values []string, dest any) *ValueBinder {
switch d := dest.(type) {
case *[]int64:
tmp := make([]int64, len(values))
for i, v := range values {
b.int(sourceParam, v, &tmp[i], 64)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
case *[]int32:
tmp := make([]int32, len(values))
for i, v := range values {
b.int(sourceParam, v, &tmp[i], 32)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
case *[]int16:
tmp := make([]int16, len(values))
for i, v := range values {
b.int(sourceParam, v, &tmp[i], 16)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
case *[]int8:
tmp := make([]int8, len(values))
for i, v := range values {
b.int(sourceParam, v, &tmp[i], 8)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
case *[]int:
tmp := make([]int, len(values))
for i, v := range values {
b.int(sourceParam, v, &tmp[i], 0)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
}
return b
}
// Int64s binds parameter to slice of int64
func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInt64s requires parameter value to exist to bind to int64 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
// Int32s binds parameter to slice of int32
func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInt32s requires parameter value to exist to bind to int32 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
// Int16s binds parameter to slice of int16
func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInt16s requires parameter value to exist to bind to int16 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
// Int8s binds parameter to slice of int8
func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInt8s requires parameter value to exist to bind to int8 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
// Ints binds parameter to slice of int
func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInts requires parameter value to exist to bind to int slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
// Uint64 binds parameter to uint64 variable
func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueBinder {
return b.uintValue(sourceParam, dest, 64, false)
}
// MustUint64 requires parameter value to exist to bind to uint64 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder {
return b.uintValue(sourceParam, dest, 64, true)
}
// Uint32 binds parameter to uint32 variable
func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueBinder {
return b.uintValue(sourceParam, dest, 32, false)
}
// MustUint32 requires parameter value to exist to bind to uint32 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder {
return b.uintValue(sourceParam, dest, 32, true)
}
// Uint16 binds parameter to uint16 variable
func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueBinder {
return b.uintValue(sourceParam, dest, 16, false)
}
// MustUint16 requires parameter value to exist to bind to uint16 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder {
return b.uintValue(sourceParam, dest, 16, true)
}
// Uint8 binds parameter to uint8 variable
func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, false)
}
// MustUint8 requires parameter value to exist to bind to uint8 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, true)
}
// Byte binds parameter to byte variable
func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, false)
}
// MustByte requires parameter value to exist to bind to byte variable. Returns error when value does not exist
func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, true)
}
// Uint binds parameter to uint variable
func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder {
return b.uintValue(sourceParam, dest, 0, false)
}
// MustUint requires parameter value to exist to bind to uint variable. Returns error when value does not exist
func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder {
return b.uintValue(sourceParam, dest, 0, true)
}
func (b *ValueBinder) uintValue(sourceParam string, dest any, bitSize int, valueMustExist bool) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
value := b.ValueFunc(sourceParam)
if value == "" {
if valueMustExist {
b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
}
return b
}
return b.uint(sourceParam, value, dest, bitSize)
}
func (b *ValueBinder) uint(sourceParam string, value string, dest any, bitSize int) *ValueBinder {
n, err := strconv.ParseUint(value, 10, bitSize)
if err != nil {
if bitSize == 0 {
b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to uint", err))
} else {
b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to uint%v", bitSize), err))
}
return b
}
switch d := dest.(type) {
case *uint64:
*d = n
case *uint32:
*d = uint32(n) // #nosec G115
case *uint16:
*d = uint16(n) // #nosec G115
case *uint8: // byte is alias to uint8
*d = uint8(n) // #nosec G115
case *uint:
*d = uint(n) // #nosec G115
}
return b
}
func (b *ValueBinder) uintsValue(sourceParam string, dest any, valueMustExist bool) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
values := b.ValuesFunc(sourceParam)
if len(values) == 0 {
if valueMustExist {
b.setError(b.ErrorFunc(sourceParam, values, "required field value is empty", nil))
}
return b
}
return b.uints(sourceParam, values, dest)
}
func (b *ValueBinder) uints(sourceParam string, values []string, dest any) *ValueBinder {
switch d := dest.(type) {
case *[]uint64:
tmp := make([]uint64, len(values))
for i, v := range values {
b.uint(sourceParam, v, &tmp[i], 64)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
case *[]uint32:
tmp := make([]uint32, len(values))
for i, v := range values {
b.uint(sourceParam, v, &tmp[i], 32)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
case *[]uint16:
tmp := make([]uint16, len(values))
for i, v := range values {
b.uint(sourceParam, v, &tmp[i], 16)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
case *[]uint8: // byte is alias to uint8
tmp := make([]uint8, len(values))
for i, v := range values {
b.uint(sourceParam, v, &tmp[i], 8)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
case *[]uint:
tmp := make([]uint, len(values))
for i, v := range values {
b.uint(sourceParam, v, &tmp[i], 0)
if b.failFast && b.errors != nil {
return b
}
}
if b.errors == nil {
*d = tmp
}
}
return b
}
// Uint64s binds parameter to slice of uint64
func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *ValueBinder {
return b.uintsValue(sourceParam, dest, false)
}
// MustUint64s requires parameter value to exist to bind to uint64 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder {
return b.uintsValue(sourceParam, dest, true)
}
// Uint32s binds parameter to slice of uint32
func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *ValueBinder {
return b.uintsValue(sourceParam, dest, false)
}
// MustUint32s requires parameter value to exist to bind to uint32 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder {
return b.uintsValue(sourceParam, dest, true)
}
// Uint16s binds parameter to slice of uint16
func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *ValueBinder {
return b.uintsValue(sourceParam, dest, false)
}
// MustUint16s requires parameter value to exist to bind to uint16 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder {
return b.uintsValue(sourceParam, dest, true)
}
// Uint8s binds parameter to slice of uint8
func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *ValueBinder {
return b.uintsValue(source
gitextract_q3zebxle/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE.md │ ├── stale.yml │ └── workflows/ │ ├── checks.yml │ └── echo.yml ├── .gitignore ├── API_CHANGES_V5.md ├── CHANGELOG.md ├── CLAUDE.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── _fixture/ │ ├── _fixture/ │ │ └── README.md │ ├── certs/ │ │ ├── README.md │ │ ├── cert.pem │ │ └── key.pem │ ├── dist/ │ │ ├── private.txt │ │ └── public/ │ │ ├── assets/ │ │ │ ├── readme.md │ │ │ └── subfolder/ │ │ │ └── subfolder.md │ │ ├── index.html │ │ └── test.txt │ ├── folder/ │ │ └── index.html │ └── index.html ├── bind.go ├── bind_test.go ├── binder.go ├── binder_external_test.go ├── binder_generic.go ├── binder_generic_test.go ├── binder_test.go ├── codecov.yml ├── context.go ├── context_generic.go ├── context_generic_test.go ├── context_test.go ├── echo.go ├── echo_test.go ├── echotest/ │ ├── context.go │ ├── context_external_test.go │ ├── context_test.go │ ├── reader.go │ ├── reader_external_test.go │ ├── reader_test.go │ └── testdata/ │ └── test.json ├── go.mod ├── go.sum ├── group.go ├── group_test.go ├── httperror.go ├── httperror_external_test.go ├── httperror_test.go ├── ip.go ├── ip_test.go ├── json.go ├── json_test.go ├── middleware/ │ ├── DEVELOPMENT.md │ ├── basic_auth.go │ ├── basic_auth_test.go │ ├── body_dump.go │ ├── body_dump_test.go │ ├── body_limit.go │ ├── body_limit_test.go │ ├── compress.go │ ├── compress_test.go │ ├── context_timeout.go │ ├── context_timeout_test.go │ ├── cors.go │ ├── cors_test.go │ ├── csrf.go │ ├── csrf_test.go │ ├── decompress.go │ ├── decompress_test.go │ ├── extractor.go │ ├── extractor_test.go │ ├── key_auth.go │ ├── key_auth_test.go │ ├── method_override.go │ ├── method_override_test.go │ ├── middleware.go │ ├── middleware_test.go │ ├── proxy.go │ ├── proxy_test.go │ ├── rate_limiter.go │ ├── rate_limiter_test.go │ ├── recover.go │ ├── recover_test.go │ ├── redirect.go │ ├── redirect_test.go │ ├── request_id.go │ ├── request_id_test.go │ ├── request_logger.go │ ├── request_logger_test.go │ ├── rewrite.go │ ├── rewrite_test.go │ ├── secure.go │ ├── secure_test.go │ ├── slash.go │ ├── slash_test.go │ ├── static.go │ ├── static_other.go │ ├── static_test.go │ ├── testdata/ │ │ ├── dist/ │ │ │ ├── private.txt │ │ │ └── public/ │ │ │ ├── assets/ │ │ │ │ ├── readme.md │ │ │ │ └── subfolder/ │ │ │ │ └── subfolder.md │ │ │ ├── index.html │ │ │ └── test.txt │ │ └── private.txt │ ├── util.go │ └── util_test.go ├── renderer.go ├── renderer_test.go ├── response.go ├── response_test.go ├── route.go ├── route_test.go ├── router.go ├── router_concurrent.go ├── router_concurrent_test.go ├── router_test.go ├── server.go ├── server_test.go ├── version.go ├── vhost.go └── vhost_test.go
SYMBOL INDEX (1412 symbols across 90 files)
FILE: bind.go
type Binder (line 19) | type Binder interface
type DefaultBinder (line 24) | type DefaultBinder struct
method Bind (line 122) | func (b *DefaultBinder) Bind(c *Context, target any) error {
type BindUnmarshaler (line 29) | type BindUnmarshaler interface
type bindMultipleUnmarshaler (line 37) | type bindMultipleUnmarshaler interface
function BindPathValues (line 42) | func BindPathValues(c *Context, target any) error {
function BindQueryParams (line 54) | func BindQueryParams(c *Context, target any) error {
function BindBody (line 66) | func BindBody(c *Context, target any) (err error) {
function BindHeaders (line 112) | func BindHeaders(c *Context, target any) error {
function bindData (line 139) | func bindData(destination any, data map[string][]string, tag string, dat...
function setWithProperType (line 292) | func setWithProperType(valueKind reflect.Kind, val string, structField r...
function unmarshalInputsToField (line 336) | func unmarshalInputsToField(valueKind reflect.Kind, values []string, fie...
function unmarshalInputToField (line 352) | func unmarshalInputToField(valueKind reflect.Kind, val string, field ref...
function setIntField (line 383) | func setIntField(value string, bitSize int, field reflect.Value) error {
function setUintField (line 394) | func setUintField(value string, bitSize int, field reflect.Value) error {
function setBoolField (line 405) | func setBoolField(value string, field reflect.Value) error {
function setFloatField (line 416) | func setFloatField(value string, bitSize int, field reflect.Value) error {
function isFieldMultipartFile (line 436) | func isFieldMultipartFile(field reflect.Type) (bool, error) {
function setMultipartFileHeaderTypes (line 449) | func setMultipartFileHeaderTypes(structField reflect.Value, inputFieldNa...
FILE: bind_test.go
type bindTestStruct (line 27) | type bindTestStruct struct
method GetCantSet (line 131) | func (t bindTestStruct) GetCantSet() string {
type bindTestStructWithTags (line 65) | type bindTestStructWithTags struct
type Timestamp (line 103) | type Timestamp
method UnmarshalParam (line 113) | func (t *Timestamp) UnmarshalParam(src string) error {
type TA (line 104) | type TA
type StringArray (line 105) | type StringArray
method UnmarshalParam (line 119) | func (a *StringArray) UnmarshalParam(src string) error {
type Struct (line 106) | type Struct struct
method UnmarshalParam (line 124) | func (s *Struct) UnmarshalParam(src string) error {
type Bar (line 109) | type Bar struct
function ptr (line 173) | func ptr[T any](value T) *T {
function TestToMultipleFields (line 177) | func TestToMultipleFields(t *testing.T) {
function TestBindJSON (line 202) | func TestBindJSON(t *testing.T) {
function TestBindXML (line 211) | func TestBindXML(t *testing.T) {
function TestBindForm (line 226) | func TestBindForm(t *testing.T) {
function TestBindQueryParams (line 239) | func TestBindQueryParams(t *testing.T) {
function TestBindQueryParamsCaseInsensitive (line 252) | func TestBindQueryParamsCaseInsensitive(t *testing.T) {
function TestBindQueryParamsCaseSensitivePrioritized (line 265) | func TestBindQueryParamsCaseSensitivePrioritized(t *testing.T) {
function TestBindHeaderParam (line 278) | func TestBindHeaderParam(t *testing.T) {
function TestBindHeaderParamBadType (line 293) | func TestBindHeaderParamBadType(t *testing.T) {
function TestBindUnmarshalParam (line 309) | func TestBindUnmarshalParam(t *testing.T) {
function TestBindUnmarshalText (line 336) | func TestBindUnmarshalText(t *testing.T) {
function TestBindUnmarshalParamPtr (line 358) | func TestBindUnmarshalParamPtr(t *testing.T) {
function TestBindUnmarshalParamAnonymousFieldPtr (line 372) | func TestBindUnmarshalParamAnonymousFieldPtr(t *testing.T) {
function TestBindUnmarshalParamAnonymousFieldPtrNil (line 386) | func TestBindUnmarshalParamAnonymousFieldPtrNil(t *testing.T) {
function TestBindUnmarshalParamAnonymousFieldPtrCustomTag (line 400) | func TestBindUnmarshalParamAnonymousFieldPtrCustomTag(t *testing.T) {
function TestBindUnmarshalTextPtr (line 412) | func TestBindUnmarshalTextPtr(t *testing.T) {
function TestBindMultipartForm (line 426) | func TestBindMultipartForm(t *testing.T) {
function TestBindUnsupportedMediaType (line 438) | func TestBindUnsupportedMediaType(t *testing.T) {
function TestDefaultBinder_bindDataToMap (line 442) | func TestDefaultBinder_bindDataToMap(t *testing.T) {
function TestBindbindData (line 545) | func TestBindbindData(t *testing.T) {
function TestBindParam (line 567) | func TestBindParam(t *testing.T) {
function TestBindUnmarshalTypeError (line 627) | func TestBindUnmarshalTypeError(t *testing.T) {
function TestBindSetWithProperType (line 642) | func TestBindSetWithProperType(t *testing.T) {
function BenchmarkBindbindDataWithTags (line 670) | func BenchmarkBindbindDataWithTags(b *testing.B) {
function assertBindTestStruct (line 682) | func assertBindTestStruct(tb testing.TB, ts *bindTestStruct) {
function testBindOkay (line 700) | func testBindOkay(t *testing.T, r io.Reader, query url.Values, ctype str...
function testBindArrayOkay (line 718) | func testBindArrayOkay(t *testing.T, r io.Reader, query url.Values, ctyp...
function testBindError (line 737) | func testBindError(t *testing.T, r io.Reader, ctype string, expectedInte...
function TestDefaultBinder_BindToStructFromMixedSources (line 761) | func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
function TestDefaultBinder_BindBody (line 932) | func TestDefaultBinder_BindBody(t *testing.T) {
function testBindURL (line 1147) | func testBindURL(queryString string, target any) error {
type unixTimestamp (line 1155) | type unixTimestamp struct
method UnmarshalParam (line 1159) | func (t *unixTimestamp) UnmarshalParam(param string) error {
type IntArrayA (line 1168) | type IntArrayA
method UnmarshalParam (line 1172) | func (i *IntArrayA) UnmarshalParam(value string) error {
function TestBindUnmarshalParamExtras (line 1189) | func TestBindUnmarshalParamExtras(t *testing.T) {
type unixTimestampLast (line 1260) | type unixTimestampLast struct
method UnmarshalParams (line 1265) | func (t *unixTimestampLast) UnmarshalParams(params []string) error {
type IntArrayB (line 1275) | type IntArrayB
method UnmarshalParams (line 1277) | func (i *IntArrayB) UnmarshalParams(params []string) error {
function TestBindUnmarshalParams (line 1295) | func TestBindUnmarshalParams(t *testing.T) {
function TestBindInt8 (line 1364) | func TestBindInt8(t *testing.T) {
function TestBindMultipartFormFiles (line 1466) | func TestBindMultipartFormFiles(t *testing.T) {
type testFormFile (line 1529) | type testFormFile struct
function createTestFormFile (line 1535) | func createTestFormFile(formFieldName string, filename string) testFormF...
function bindMultipartFiles (line 1543) | func bindMultipartFiles(t *testing.T, target any, files ...testFormFile)...
function assertMultipartFileHeader (line 1570) | func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, f...
function TestTimeFormatBinding (line 1582) | func TestTimeFormatBinding(t *testing.T) {
FILE: binder.go
type BindingError (line 69) | type BindingError struct
method Error (line 87) | func (be *BindingError) Error() string {
function NewBindingError (line 78) | func NewBindingError(sourceParam string, values []string, message string...
type ValueBinder (line 92) | type ValueBinder struct
method FailFast (line 172) | func (b *ValueBinder) FailFast(value bool) *ValueBinder {
method setError (line 177) | func (b *ValueBinder) setError(err error) {
method BindError (line 186) | func (b *ValueBinder) BindError() error {
method BindErrors (line 196) | func (b *ValueBinder) BindErrors() []error {
method CustomFunc (line 206) | func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(v...
method MustCustomFunc (line 211) | func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc fu...
method customFunc (line 215) | func (b *ValueBinder) customFunc(sourceParam string, customFunc func(v...
method String (line 234) | func (b *ValueBinder) String(sourceParam string, dest *string) *ValueB...
method MustString (line 248) | func (b *ValueBinder) MustString(sourceParam string, dest *string) *Va...
method Strings (line 263) | func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *Val...
method MustStrings (line 277) | func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) ...
method BindUnmarshaler (line 292) | func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnm...
method MustBindUnmarshaler (line 310) | func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest Bin...
method JSONUnmarshaler (line 328) | func (b *ValueBinder) JSONUnmarshaler(sourceParam string, dest json.Un...
method MustJSONUnmarshaler (line 346) | func (b *ValueBinder) MustJSONUnmarshaler(sourceParam string, dest jso...
method TextUnmarshaler (line 364) | func (b *ValueBinder) TextUnmarshaler(sourceParam string, dest encodin...
method MustTextUnmarshaler (line 382) | func (b *ValueBinder) MustTextUnmarshaler(sourceParam string, dest enc...
method BindWithDelimiter (line 401) | func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest any, ...
method MustBindWithDelimiter (line 407) | func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest a...
method bindWithDelimiter (line 411) | func (b *ValueBinder) bindWithDelimiter(sourceParam string, dest any, ...
method Int64 (line 450) | func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBin...
method MustInt64 (line 455) | func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *Valu...
method Int32 (line 460) | func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBin...
method MustInt32 (line 465) | func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *Valu...
method Int16 (line 470) | func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBin...
method MustInt16 (line 475) | func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *Valu...
method Int8 (line 480) | func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder {
method MustInt8 (line 485) | func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueB...
method Int (line 490) | func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder {
method MustInt (line 495) | func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBin...
method intValue (line 499) | func (b *ValueBinder) intValue(sourceParam string, dest any, bitSize i...
method int (line 515) | func (b *ValueBinder) int(sourceParam string, value string, dest any, ...
method intsValue (line 541) | func (b *ValueBinder) intsValue(sourceParam string, dest any, valueMus...
method ints (line 556) | func (b *ValueBinder) ints(sourceParam string, values []string, dest a...
method Int64s (line 618) | func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *Value...
method MustInt64s (line 623) | func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *V...
method Int32s (line 628) | func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *Value...
method MustInt32s (line 633) | func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *V...
method Int16s (line 638) | func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *Value...
method MustInt16s (line 643) | func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *V...
method Int8s (line 648) | func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBi...
method MustInt8s (line 653) | func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *Val...
method Ints (line 658) | func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBind...
method MustInts (line 663) | func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *Value...
method Uint64 (line 668) | func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueB...
method MustUint64 (line 673) | func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *Va...
method Uint32 (line 678) | func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueB...
method MustUint32 (line 683) | func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *Va...
method Uint16 (line 688) | func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueB...
method MustUint16 (line 693) | func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *Va...
method Uint8 (line 698) | func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBin...
method MustUint8 (line 703) | func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *Valu...
method Byte (line 708) | func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder {
method MustByte (line 713) | func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueB...
method Uint (line 718) | func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder {
method MustUint (line 723) | func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueB...
method uintValue (line 727) | func (b *ValueBinder) uintValue(sourceParam string, dest any, bitSize ...
method uint (line 743) | func (b *ValueBinder) uint(sourceParam string, value string, dest any,...
method uintsValue (line 769) | func (b *ValueBinder) uintsValue(sourceParam string, dest any, valueMu...
method uints (line 784) | func (b *ValueBinder) uints(sourceParam string, values []string, dest ...
method Uint64s (line 846) | func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *Val...
method MustUint64s (line 851) | func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) ...
method Uint32s (line 856) | func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *Val...
method MustUint32s (line 861) | func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) ...
method Uint16s (line 866) | func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *Val...
method MustUint16s (line 871) | func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) ...
method Uint8s (line 876) | func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *Value...
method MustUint8s (line 881) | func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *V...
method Uints (line 886) | func (b *ValueBinder) Uints(sourceParam string, dest *[]uint) *ValueBi...
method MustUints (line 891) | func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *Val...
method Bool (line 896) | func (b *ValueBinder) Bool(sourceParam string, dest *bool) *ValueBinder {
method MustBool (line 901) | func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueB...
method boolValue (line 905) | func (b *ValueBinder) boolValue(sourceParam string, dest *bool, valueM...
method bool (line 920) | func (b *ValueBinder) bool(sourceParam string, value string, dest *boo...
method boolsValue (line 931) | func (b *ValueBinder) boolsValue(sourceParam string, dest *[]bool, val...
method bools (line 946) | func (b *ValueBinder) bools(sourceParam string, values []string, dest ...
method Bools (line 961) | func (b *ValueBinder) Bools(sourceParam string, dest *[]bool) *ValueBi...
method MustBools (line 966) | func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *Val...
method Float64 (line 971) | func (b *ValueBinder) Float64(sourceParam string, dest *float64) *Valu...
method MustFloat64 (line 976) | func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *...
method Float32 (line 981) | func (b *ValueBinder) Float32(sourceParam string, dest *float32) *Valu...
method MustFloat32 (line 986) | func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *...
method floatValue (line 990) | func (b *ValueBinder) floatValue(sourceParam string, dest any, bitSize...
method float (line 1006) | func (b *ValueBinder) float(sourceParam string, value string, dest any...
method floatsValue (line 1022) | func (b *ValueBinder) floatsValue(sourceParam string, dest any, valueM...
method floats (line 1037) | func (b *ValueBinder) floats(sourceParam string, values []string, dest...
method Float64s (line 1066) | func (b *ValueBinder) Float64s(sourceParam string, dest *[]float64) *V...
method MustFloat64s (line 1071) | func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64...
method Float32s (line 1076) | func (b *ValueBinder) Float32s(sourceParam string, dest *[]float32) *V...
method MustFloat32s (line 1081) | func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32...
method Time (line 1086) | func (b *ValueBinder) Time(sourceParam string, dest *time.Time, layout...
method MustTime (line 1091) | func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, la...
method time (line 1095) | func (b *ValueBinder) time(sourceParam string, dest *time.Time, layout...
method Times (line 1117) | func (b *ValueBinder) Times(sourceParam string, dest *[]time.Time, lay...
method MustTimes (line 1122) | func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time,...
method times (line 1126) | func (b *ValueBinder) times(sourceParam string, dest *[]time.Time, lay...
method Duration (line 1158) | func (b *ValueBinder) Duration(sourceParam string, dest *time.Duration...
method MustDuration (line 1163) | func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Dura...
method duration (line 1167) | func (b *ValueBinder) duration(sourceParam string, dest *time.Duration...
method Durations (line 1189) | func (b *ValueBinder) Durations(sourceParam string, dest *[]time.Durat...
method MustDurations (line 1194) | func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.D...
method durationsValue (line 1198) | func (b *ValueBinder) durationsValue(sourceParam string, dest *[]time....
method durations (line 1213) | func (b *ValueBinder) durations(sourceParam string, values []string, d...
method UnixTime (line 1238) | func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *V...
method MustUnixTime (line 1249) | func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time...
method UnixTimeMilli (line 1259) | func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Tim...
method MustUnixTimeMilli (line 1270) | func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time...
method UnixTimeNano (line 1283) | func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time...
method MustUnixTimeNano (line 1297) | func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time....
method unixTime (line 1301) | func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, va...
function QueryParamsBinder (line 105) | func QueryParamsBinder(c *Context) *ValueBinder {
function PathValuesBinder (line 121) | func PathValuesBinder(c *Context) *ValueBinder {
function FormFieldBinder (line 147) | func FormFieldBinder(c *Context) *ValueBinder {
FILE: binder_external_test.go
function ExampleValueBinder_BindErrors (line 17) | func ExampleValueBinder_BindErrors() {
function ExampleValueBinder_BindError (line 55) | func ExampleValueBinder_BindError() {
function ExampleValueBinder_CustomFunc (line 91) | func ExampleValueBinder_CustomFunc() {
FILE: binder_generic.go
type TimeLayout (line 16) | type TimeLayout
type TimeOpts (line 19) | type TimeOpts struct
constant TimeLayoutUnixTime (line 43) | TimeLayoutUnixTime = TimeLayout("UnixTime")
constant TimeLayoutUnixTimeMilli (line 44) | TimeLayoutUnixTimeMilli = TimeLayout("UnixTimeMilli")
constant TimeLayoutUnixTimeNano (line 45) | TimeLayoutUnixTimeNano = TimeLayout("UnixTimeNano")
function PathParam (line 58) | func PathParam[T any](c *Context, paramName string, opts ...any) (T, err...
function PathParamOr (line 83) | func PathParamOr[T any](c *Context, paramName string, defaultValue T, op...
function QueryParam (line 111) | func QueryParam[T any](c *Context, key string, opts ...any) (T, error) {
function QueryParamOr (line 140) | func QueryParamOr[T any](c *Context, key string, defaultValue T, opts .....
function QueryParams (line 160) | func QueryParams[T any](c *Context, key string, opts ...any) ([]T, error) {
function QueryParamsOr (line 184) | func QueryParamsOr[T any](c *Context, key string, defaultValue []T, opts...
function FormValue (line 207) | func FormValue[T any](c *Context, key string, opts ...any) (T, error) {
function FormValueOr (line 241) | func FormValueOr[T any](c *Context, key string, defaultValue T, opts ......
function FormValues (line 266) | func FormValues[T any](c *Context, key string, opts ...any) ([]T, error) {
function FormValuesOr (line 292) | func FormValuesOr[T any](c *Context, key string, defaultValue []T, opts ...
function ParseValues (line 312) | func ParseValues[T any](values []string, opts ...any) ([]T, error) {
function ParseValuesOr (line 321) | func ParseValuesOr[T any](values []string, defaultValue []T, opts ...any...
function ParseValue (line 358) | func ParseValue[T any](value string, opts ...any) (T, error) {
function ParseValueOr (line 385) | func ParseValueOr[T any](value string, defaultValue T, opts ...any) (T, ...
function bindValue (line 397) | func bindValue(value string, dest any, opts ...any) error {
FILE: binder_generic_test.go
type TextUnmarshalerType (line 21) | type TextUnmarshalerType struct
method UnmarshalText (line 25) | func (t *TextUnmarshalerType) UnmarshalText(data []byte) error {
type JSONUnmarshalerType (line 35) | type JSONUnmarshalerType struct
method UnmarshalJSON (line 39) | func (j *JSONUnmarshalerType) UnmarshalJSON(data []byte) error {
function TestPathParam (line 43) | func TestPathParam(t *testing.T) {
function TestPathParam_UnsupportedType (line 89) | func TestPathParam_UnsupportedType(t *testing.T) {
function TestQueryParam (line 100) | func TestQueryParam(t *testing.T) {
function TestQueryParam_UnsupportedType (line 141) | func TestQueryParam_UnsupportedType(t *testing.T) {
function TestQueryParams (line 152) | func TestQueryParams(t *testing.T) {
function TestQueryParams_UnsupportedType (line 193) | func TestQueryParams_UnsupportedType(t *testing.T) {
function TestFormValue (line 204) | func TestFormValue(t *testing.T) {
function TestFormValue_UnsupportedType (line 245) | func TestFormValue_UnsupportedType(t *testing.T) {
function TestFormValues (line 256) | func TestFormValues(t *testing.T) {
function TestFormValues_UnsupportedType (line 297) | func TestFormValues_UnsupportedType(t *testing.T) {
function TestParseValue_bool (line 308) | func TestParseValue_bool(t *testing.T) {
function TestParseValue_float32 (line 349) | func TestParseValue_float32(t *testing.T) {
function TestParseValue_float64 (line 407) | func TestParseValue_float64(t *testing.T) {
function TestParseValue_int (line 465) | func TestParseValue_int(t *testing.T) {
function TestParseValue_uint (line 529) | func TestParseValue_uint(t *testing.T) {
function TestParseValue_int8 (line 583) | func TestParseValue_int8(t *testing.T) {
function TestParseValue_int16 (line 647) | func TestParseValue_int16(t *testing.T) {
function TestParseValue_int32 (line 711) | func TestParseValue_int32(t *testing.T) {
function TestParseValue_int64 (line 775) | func TestParseValue_int64(t *testing.T) {
function TestParseValue_uint8 (line 839) | func TestParseValue_uint8(t *testing.T) {
function TestParseValue_uint16 (line 893) | func TestParseValue_uint16(t *testing.T) {
function TestParseValue_uint32 (line 947) | func TestParseValue_uint32(t *testing.T) {
function TestParseValue_uint64 (line 1001) | func TestParseValue_uint64(t *testing.T) {
function TestParseValue_string (line 1055) | func TestParseValue_string(t *testing.T) {
function TestParseValue_Duration (line 1086) | func TestParseValue_Duration(t *testing.T) {
function TestParseValue_Time (line 1123) | func TestParseValue_Time(t *testing.T) {
function TestParseValue_OptionsOnlyForTime (line 1241) | func TestParseValue_OptionsOnlyForTime(t *testing.T) {
function TestParseValue_BindUnmarshaler (line 1246) | func TestParseValue_BindUnmarshaler(t *testing.T) {
function TestParseValue_TextUnmarshaler (line 1280) | func TestParseValue_TextUnmarshaler(t *testing.T) {
function TestParseValue_JSONUnmarshaler (line 1317) | func TestParseValue_JSONUnmarshaler(t *testing.T) {
function TestParseValues_bools (line 1360) | func TestParseValues_bools(t *testing.T) {
function TestPathParamOr (line 1392) | func TestPathParamOr(t *testing.T) {
function TestQueryParamOr (line 1446) | func TestQueryParamOr(t *testing.T) {
function TestQueryParamsOr (line 1495) | func TestQueryParamsOr(t *testing.T) {
function TestFormValueOr (line 1538) | func TestFormValueOr(t *testing.T) {
function TestFormValuesOr (line 1581) | func TestFormValuesOr(t *testing.T) {
FILE: binder_test.go
function createTestContext (line 21) | func createTestContext(URL string, body io.Reader, pathValues map[string...
function TestBindingError_Error (line 44) | func TestBindingError_Error(t *testing.T) {
function TestBindingError_ErrorJSON (line 57) | func TestBindingError_ErrorJSON(t *testing.T) {
function TestPathValuesBinder (line 65) | func TestPathValuesBinder(t *testing.T) {
function TestQueryParamsBinder_FailFast (line 90) | func TestQueryParamsBinder_FailFast(t *testing.T) {
function TestFormFieldBinder (line 134) | func TestFormFieldBinder(t *testing.T) {
function TestValueBinder_errorStopsBinding (line 167) | func TestValueBinder_errorStopsBinding(t *testing.T) {
function TestValueBinder_BindError (line 185) | func TestValueBinder_BindError(t *testing.T) {
function TestValueBinder_GetValues (line 200) | func TestValueBinder_GetValues(t *testing.T) {
function TestValueBinder_CustomFuncWithError (line 248) | func TestValueBinder_CustomFuncWithError(t *testing.T) {
function TestValueBinder_CustomFunc (line 267) | func TestValueBinder_CustomFunc(t *testing.T) {
function TestValueBinder_MustCustomFunc (line 342) | func TestValueBinder_MustCustomFunc(t *testing.T) {
function TestValueBinder_String (line 418) | func TestValueBinder_String(t *testing.T) {
function TestValueBinder_Strings (line 494) | func TestValueBinder_Strings(t *testing.T) {
function TestValueBinder_Int64_intValue (line 570) | func TestValueBinder_Int64_intValue(t *testing.T) {
function TestValueBinder_Int_errorMessage (line 659) | func TestValueBinder_Int_errorMessage(t *testing.T) {
function TestValueBinder_Uint64_uintValue (line 674) | func TestValueBinder_Uint64_uintValue(t *testing.T) {
function TestValueBinder_Int_Types (line 763) | func TestValueBinder_Int_Types(t *testing.T) {
function TestValueBinder_Int64s_intsValue (line 881) | func TestValueBinder_Int64s_intsValue(t *testing.T) {
function TestValueBinder_Uint64s_uintsValue (line 970) | func TestValueBinder_Uint64s_uintsValue(t *testing.T) {
function TestValueBinder_Ints_Types (line 1059) | func TestValueBinder_Ints_Types(t *testing.T) {
function TestValueBinder_Ints_Types_FailFast (line 1170) | func TestValueBinder_Ints_Types_FailFast(t *testing.T) {
function TestValueBinder_Bool (line 1226) | func TestValueBinder_Bool(t *testing.T) {
function TestValueBinder_Bools (line 1315) | func TestValueBinder_Bools(t *testing.T) {
function TestValueBinder_Float64 (line 1411) | func TestValueBinder_Float64(t *testing.T) {
function TestValueBinder_Float64s (line 1500) | func TestValueBinder_Float64s(t *testing.T) {
function TestValueBinder_Float32 (line 1596) | func TestValueBinder_Float32(t *testing.T) {
function TestValueBinder_Float32s (line 1685) | func TestValueBinder_Float32s(t *testing.T) {
function TestValueBinder_Time (line 1781) | func TestValueBinder_Time(t *testing.T) {
function TestValueBinder_Times (line 1861) | func TestValueBinder_Times(t *testing.T) {
function TestValueBinder_Duration (line 1947) | func TestValueBinder_Duration(t *testing.T) {
function TestValueBinder_Durations (line 2024) | func TestValueBinder_Durations(t *testing.T) {
function TestValueBinder_BindUnmarshaler (line 2102) | func TestValueBinder_BindUnmarshaler(t *testing.T) {
function TestValueBinder_JSONUnmarshaler (line 2193) | func TestValueBinder_JSONUnmarshaler(t *testing.T) {
function TestValueBinder_TextUnmarshaler (line 2284) | func TestValueBinder_TextUnmarshaler(t *testing.T) {
function TestValueBinder_BindWithDelimiter_types (line 2375) | func TestValueBinder_BindWithDelimiter_types(t *testing.T) {
function TestValueBinder_BindWithDelimiter (line 2522) | func TestValueBinder_BindWithDelimiter(t *testing.T) {
function TestBindWithDelimiter_invalidType (line 2611) | func TestBindWithDelimiter_invalidType(t *testing.T) {
function TestValueBinder_UnixTime (line 2621) | func TestValueBinder_UnixTime(t *testing.T) {
function TestValueBinder_UnixTimeMilli (line 2717) | func TestValueBinder_UnixTimeMilli(t *testing.T) {
function TestValueBinder_UnixTimeNano (line 2808) | func TestValueBinder_UnixTimeNano(t *testing.T) {
function BenchmarkDefaultBinder_BindInt64_single (line 2911) | func BenchmarkDefaultBinder_BindInt64_single(b *testing.B) {
function BenchmarkValueBinder_BindInt64_single (line 2926) | func BenchmarkValueBinder_BindInt64_single(b *testing.B) {
function BenchmarkRawFunc_Int64_single (line 2941) | func BenchmarkRawFunc_Int64_single(b *testing.B) {
function BenchmarkDefaultBinder_BindInt64_10_fields (line 2968) | func BenchmarkDefaultBinder_BindInt64_10_fields(b *testing.B) {
function BenchmarkValueBinder_BindInt64_10_fields (line 2995) | func BenchmarkValueBinder_BindInt64_10_fields(b *testing.B) {
function TestValueBinder_TimeError (line 3033) | func TestValueBinder_TimeError(t *testing.T) {
function TestValueBinder_TimesError (line 3085) | func TestValueBinder_TimesError(t *testing.T) {
function TestValueBinder_DurationError (line 3147) | func TestValueBinder_DurationError(t *testing.T) {
function TestValueBinder_DurationsError (line 3198) | func TestValueBinder_DurationsError(t *testing.T) {
FILE: context.go
constant ContextKeyHeaderAllow (line 28) | ContextKeyHeaderAllow = "echo_header_allow"
constant defaultMemory (line 34) | defaultMemory int64 = 32 << 20
constant indexPage (line 35) | indexPage = "index.html"
type Context (line 40) | type Context struct
method Reset (line 107) | func (c *Context) Reset(r *http.Request, w http.ResponseWriter) {
method writeContentType (line 121) | func (c *Context) writeContentType(value string) {
method Request (line 129) | func (c *Context) Request() *http.Request {
method SetRequest (line 134) | func (c *Context) SetRequest(r *http.Request) {
method Response (line 139) | func (c *Context) Response() http.ResponseWriter {
method SetResponse (line 145) | func (c *Context) SetResponse(r http.ResponseWriter) {
method IsTLS (line 150) | func (c *Context) IsTLS() bool {
method IsWebSocket (line 155) | func (c *Context) IsWebSocket() bool {
method Scheme (line 162) | func (c *Context) Scheme() string {
method RealIP (line 186) | func (c *Context) RealIP() string {
method Path (line 211) | func (c *Context) Path() string {
method SetPath (line 216) | func (c *Context) SetPath(p string) {
method RouteInfo (line 226) | func (c *Context) RouteInfo() RouteInfo {
method Param (line 234) | func (c *Context) Param(name string) string {
method ParamOr (line 246) | func (c *Context) ParamOr(name, defaultValue string) string {
method PathValues (line 251) | func (c *Context) PathValues() PathValues {
method SetPathValues (line 256) | func (c *Context) SetPathValues(pathValues PathValues) {
method InitializeRoute (line 264) | func (c *Context) InitializeRoute(ri *RouteInfo, pathValues *PathValue...
method setPathValues (line 270) | func (c *Context) setPathValues(pv *PathValues) {
method QueryParam (line 288) | func (c *Context) QueryParam(name string) string {
method QueryParamOr (line 298) | func (c *Context) QueryParamOr(name, defaultValue string) string {
method QueryParams (line 307) | func (c *Context) QueryParams() url.Values {
method QueryString (line 315) | func (c *Context) QueryString() string {
method FormValue (line 320) | func (c *Context) FormValue(name string) string {
method FormValueOr (line 326) | func (c *Context) FormValueOr(name, defaultValue string) string {
method FormValues (line 335) | func (c *Context) FormValues() (url.Values, error) {
method FormFile (line 349) | func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
method MultipartForm (line 359) | func (c *Context) MultipartForm() (*multipart.Form, error) {
method Cookie (line 365) | func (c *Context) Cookie(name string) (*http.Cookie, error) {
method SetCookie (line 370) | func (c *Context) SetCookie(cookie *http.Cookie) {
method Cookies (line 375) | func (c *Context) Cookies() []*http.Cookie {
method Get (line 381) | func (c *Context) Get(key string) any {
method Set (line 388) | func (c *Context) Set(key string, val any) {
method Bind (line 400) | func (c *Context) Bind(i any) error {
method Validate (line 406) | func (c *Context) Validate(i any) error {
method Render (line 415) | func (c *Context) Render(code int, name string, data any) (err error) {
method HTML (line 436) | func (c *Context) HTML(code int, html string) (err error) {
method HTMLBlob (line 441) | func (c *Context) HTMLBlob(code int, b []byte) (err error) {
method String (line 446) | func (c *Context) String(code int, s string) (err error) {
method jsonPBlob (line 450) | func (c *Context) jsonPBlob(code int, callback string, i any) (err err...
method json (line 465) | func (c *Context) json(code int, i any, indent string) error {
method JSON (line 483) | func (c *Context) JSON(code int, i any) (err error) {
method JSONPretty (line 488) | func (c *Context) JSONPretty(code int, i any, indent string) (err erro...
method JSONBlob (line 493) | func (c *Context) JSONBlob(code int, b []byte) (err error) {
method JSONP (line 499) | func (c *Context) JSONP(code int, callback string, i any) (err error) {
method JSONPBlob (line 505) | func (c *Context) JSONPBlob(code int, callback string, b []byte) (err ...
method xml (line 518) | func (c *Context) xml(code int, i any, indent string) (err error) {
method XML (line 532) | func (c *Context) XML(code int, i any) (err error) {
method XMLPretty (line 537) | func (c *Context) XMLPretty(code int, i any, indent string) (err error) {
method XMLBlob (line 542) | func (c *Context) XMLBlob(code int, b []byte) (err error) {
method Blob (line 553) | func (c *Context) Blob(code int, contentType string, b []byte) (err er...
method Stream (line 561) | func (c *Context) Stream(code int, contentType string, r io.Reader) (e...
method File (line 569) | func (c *Context) File(file string) error {
method FileFS (line 578) | func (c *Context) FileFS(file string, filesystem fs.FS) error {
method Attachment (line 611) | func (c *Context) Attachment(file, name string) error {
method Inline (line 616) | func (c *Context) Inline(file, name string) error {
method contentDisposition (line 622) | func (c *Context) contentDisposition(file, name, dispositionType strin...
method NoContent (line 628) | func (c *Context) NoContent(code int) error {
method Redirect (line 634) | func (c *Context) Redirect(code int, url string) error {
method Logger (line 644) | func (c *Context) Logger() *slog.Logger {
method SetLogger (line 652) | func (c *Context) SetLogger(logger *slog.Logger) {
method Echo (line 657) | func (c *Context) Echo() *Echo {
function NewContext (line 64) | func NewContext(r *http.Request, w http.ResponseWriter, opts ...any) *Co...
function newContext (line 75) | func newContext(r *http.Request, w http.ResponseWriter, e *Echo) *Context {
function fsFile (line 582) | func fsFile(c *Context, file string, filesystem fs.FS) error {
FILE: context_generic.go
function ContextGet (line 16) | func ContextGet[T any](c *Context, key string) (T, error) {
function ContextGetOr (line 37) | func ContextGetOr[T any](c *Context, key string, defaultValue T) (T, err...
FILE: context_generic_test.go
function TestContextGetOK (line 12) | func TestContextGetOK(t *testing.T) {
function TestContextGetNonExistentKey (line 22) | func TestContextGetNonExistentKey(t *testing.T) {
function TestContextGetInvalidCast (line 32) | func TestContextGetInvalidCast(t *testing.T) {
function TestContextGetOrOK (line 42) | func TestContextGetOrOK(t *testing.T) {
function TestContextGetOrNonExistentKey (line 52) | func TestContextGetOrNonExistentKey(t *testing.T) {
function TestContextGetOrInvalidCast (line 62) | func TestContextGetOrInvalidCast(t *testing.T) {
FILE: context_test.go
type Template (line 29) | type Template struct
method Render (line 89) | func (t *Template) Render(c *Context, w io.Writer, name string, data a...
function BenchmarkAllocJSONP (line 35) | func BenchmarkAllocJSONP(b *testing.B) {
function BenchmarkAllocJSON (line 50) | func BenchmarkAllocJSON(b *testing.B) {
function BenchmarkAllocXML (line 65) | func BenchmarkAllocXML(b *testing.B) {
function BenchmarkRealIPForHeaderXForwardFor (line 80) | func BenchmarkRealIPForHeaderXForwardFor(b *testing.B) {
function TestContextEcho (line 93) | func TestContextEcho(t *testing.T) {
function TestContextRequest (line 103) | func TestContextRequest(t *testing.T) {
function TestContextResponse (line 114) | func TestContextResponse(t *testing.T) {
function TestContextRenderTemplate (line 124) | func TestContextRenderTemplate(t *testing.T) {
function TestContextRenderTemplateError (line 142) | func TestContextRenderTemplateError(t *testing.T) {
function TestContextRenderErrorsOnNoRenderer (line 160) | func TestContextRenderErrorsOnNoRenderer(t *testing.T) {
function TestContextStream (line 171) | func TestContextStream(t *testing.T) {
function TestContextHTML (line 194) | func TestContextHTML(t *testing.T) {
function TestContextHTMLBlob (line 207) | func TestContextHTMLBlob(t *testing.T) {
function TestContextJSON (line 220) | func TestContextJSON(t *testing.T) {
function TestContextJSONErrorsOut (line 234) | func TestContextJSONErrorsOut(t *testing.T) {
function TestContextJSONWithNotEchoResponse (line 247) | func TestContextJSONWithNotEchoResponse(t *testing.T) {
function TestContextJSONPretty (line 262) | func TestContextJSONPretty(t *testing.T) {
function TestContextJSONWithEmptyIntent (line 276) | func TestContextJSONWithEmptyIntent(t *testing.T) {
function TestContextJSONP (line 297) | func TestContextJSONP(t *testing.T) {
function TestContextJSONBlob (line 312) | func TestContextJSONBlob(t *testing.T) {
function TestContextJSONPBlob (line 328) | func TestContextJSONPBlob(t *testing.T) {
function TestContextXML (line 345) | func TestContextXML(t *testing.T) {
function TestContextXMLPretty (line 359) | func TestContextXMLPretty(t *testing.T) {
function TestContextXMLBlob (line 373) | func TestContextXMLBlob(t *testing.T) {
function TestContextXMLWithEmptyIntent (line 389) | func TestContextXMLWithEmptyIntent(t *testing.T) {
function TestContext_JSON_CommitsCustomResponseCode (line 410) | func TestContext_JSON_CommitsCustomResponseCode(t *testing.T) {
function TestContextAttachment (line 424) | func TestContextAttachment(t *testing.T) {
function TestContextInline (line 459) | func TestContextInline(t *testing.T) {
function TestContextNoContent (line 494) | func TestContextNoContent(t *testing.T) {
function TestContextCookie (line 504) | func TestContextCookie(t *testing.T) {
function TestContext_PathValues (line 549) | func TestContext_PathValues(t *testing.T) {
function TestContext_PathParam (line 586) | func TestContext_PathParam(t *testing.T) {
function TestContext_PathParamDefault (line 635) | func TestContext_PathParamDefault(t *testing.T) {
function TestContextGetAndSetPathValuesMutability (line 687) | func TestContextGetAndSetPathValuesMutability(t *testing.T) {
function TestContext_SetParamNamesShouldNotModifyPathValuesCapacity (line 759) | func TestContext_SetParamNamesShouldNotModifyPathValuesCapacity(t *testi...
function TestContextFormValue (line 782) | func TestContextFormValue(t *testing.T) {
function TestContext_QueryParams (line 818) | func TestContext_QueryParams(t *testing.T) {
function TestContext_QueryParam (line 857) | func TestContext_QueryParam(t *testing.T) {
function TestContext_QueryParamDefault (line 901) | func TestContext_QueryParamDefault(t *testing.T) {
function TestContextFormFile (line 943) | func TestContextFormFile(t *testing.T) {
function TestContextMultipartForm (line 962) | func TestContextMultipartForm(t *testing.T) {
function TestContextRedirect (line 990) | func TestContextRedirect(t *testing.T) {
function TestContextGet (line 1001) | func TestContextGet(t *testing.T) {
function BenchmarkContext_Store (line 1038) | func BenchmarkContext_Store(b *testing.B) {
type validator (line 1053) | type validator struct
method Validate (line 1055) | func (*validator) Validate(i any) error {
function TestContext_Validate (line 1059) | func TestContext_Validate(t *testing.T) {
function TestContext_QueryString (line 1069) | func TestContext_QueryString(t *testing.T) {
function TestContext_Request (line 1080) | func TestContext_Request(t *testing.T) {
function TestContext_Scheme (line 1091) | func TestContext_Scheme(t *testing.T) {
function TestContext_IsWebSocket (line 1149) | func TestContext_IsWebSocket(t *testing.T) {
function TestContext_Bind (line 1210) | func TestContext_Bind(t *testing.T) {
function TestContext_RealIP (line 1222) | func TestContext_RealIP(t *testing.T) {
function TestContext_File (line 1311) | func TestContext_File(t *testing.T) {
function TestContext_FileFS (line 1377) | func TestContext_FileFS(t *testing.T) {
function TestLogger (line 1433) | func TestLogger(t *testing.T) {
function TestRouteInfo (line 1450) | func TestRouteInfo(t *testing.T) {
FILE: echo.go
type Echo (line 68) | type Echo struct
method NewContext (line 348) | func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) *Con...
method Router (line 353) | func (e *Echo) Router() Router {
method Pre (line 417) | func (e *Echo) Pre(middleware ...MiddlewareFunc) {
method Use (line 422) | func (e *Echo) Use(middleware ...MiddlewareFunc) {
method CONNECT (line 428) | func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc...
method DELETE (line 434) | func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc)...
method GET (line 440) | func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) Ro...
method HEAD (line 446) | func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) R...
method OPTIONS (line 452) | func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc...
method PATCH (line 458) | func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) ...
method POST (line 464) | func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) R...
method PUT (line 470) | func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) Ro...
method TRACE (line 476) | func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) ...
method RouteNotFound (line 486) | func (e *Echo) RouteNotFound(path string, h HandlerFunc, m ...Middlewa...
method Any (line 495) | func (e *Echo) Any(path string, handler HandlerFunc, middleware ...Mid...
method Match (line 501) | func (e *Echo) Match(methods []string, path string, handler HandlerFun...
method Static (line 524) | func (e *Echo) Static(pathPrefix, fsRoot string, middleware ...Middlew...
method StaticFS (line 539) | func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS, middlewar...
method FileFS (line 579) | func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...Middle...
method File (line 591) | func (e *Echo) File(path, file string, middleware ...MiddlewareFunc) R...
method AddRoute (line 599) | func (e *Echo) AddRoute(route Route) (RouteInfo, error) {
method add (line 603) | func (e *Echo) add(route Route) (RouteInfo, error) {
method Add (line 624) | func (e *Echo) Add(method, path string, handler HandlerFunc, middlewar...
method Group (line 641) | func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
method PreMiddlewares (line 652) | func (e *Echo) PreMiddlewares() []MiddlewareFunc {
method Middlewares (line 660) | func (e *Echo) Middlewares() []MiddlewareFunc {
method AcquireContext (line 666) | func (e *Echo) AcquireContext() *Context {
method ReleaseContext (line 672) | func (e *Echo) ReleaseContext(c *Context) {
method ServeHTTP (line 677) | func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
method serveHTTP (line 682) | func (e *Echo) serveHTTP(w http.ResponseWriter, r *http.Request) {
method Start (line 726) | func (e *Echo) Start(address string) error {
type JSONSerializer (line 98) | type JSONSerializer interface
type HTTPErrorHandler (line 104) | type HTTPErrorHandler
type HandlerFunc (line 107) | type HandlerFunc
type MiddlewareFunc (line 110) | type MiddlewareFunc
type MiddlewareConfigurator (line 113) | type MiddlewareConfigurator interface
type Validator (line 118) | type Validator interface
constant MIMEApplicationJSON (line 125) | MIMEApplicationJSON = "application/json"
constant MIMEApplicationJSONCharsetUTF8 (line 130) | MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + char...
constant MIMEApplicationJavaScript (line 131) | MIMEApplicationJavaScript = "application/javascript"
constant MIMEApplicationJavaScriptCharsetUTF8 (line 132) | MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " ...
constant MIMEApplicationXML (line 133) | MIMEApplicationXML = "application/xml"
constant MIMEApplicationXMLCharsetUTF8 (line 134) | MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + chars...
constant MIMETextXML (line 135) | MIMETextXML = "text/xml"
constant MIMETextXMLCharsetUTF8 (line 136) | MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8
constant MIMEApplicationForm (line 137) | MIMEApplicationForm = "application/x-www-form-urlencoded"
constant MIMEApplicationProtobuf (line 138) | MIMEApplicationProtobuf = "application/protobuf"
constant MIMEApplicationMsgpack (line 139) | MIMEApplicationMsgpack = "application/msgpack"
constant MIMETextHTML (line 140) | MIMETextHTML = "text/html"
constant MIMETextHTMLCharsetUTF8 (line 141) | MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
constant MIMETextPlain (line 142) | MIMETextPlain = "text/plain"
constant MIMETextPlainCharsetUTF8 (line 143) | MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
constant MIMEMultipartForm (line 144) | MIMEMultipartForm = "multipart/form-data"
constant MIMEOctetStream (line 145) | MIMEOctetStream = "application/octet-stream"
constant charsetUTF8 (line 149) | charsetUTF8 = "charset=UTF-8"
constant PROPFIND (line 151) | PROPFIND = "PROPFIND"
constant REPORT (line 153) | REPORT = "REPORT"
constant RouteNotFound (line 155) | RouteNotFound = "echo_route_not_found"
constant RouteAny (line 158) | RouteAny = "echo_route_any"
constant HeaderAccept (line 163) | HeaderAccept = "Accept"
constant HeaderAcceptEncoding (line 164) | HeaderAcceptEncoding = "Accept-Encoding"
constant HeaderAllow (line 169) | HeaderAllow = "Allow"
constant HeaderAuthorization (line 170) | HeaderAuthorization = "Authorization"
constant HeaderContentDisposition (line 171) | HeaderContentDisposition = "Content-Disposition"
constant HeaderContentEncoding (line 172) | HeaderContentEncoding = "Content-Encoding"
constant HeaderContentLength (line 173) | HeaderContentLength = "Content-Length"
constant HeaderContentType (line 174) | HeaderContentType = "Content-Type"
constant HeaderCookie (line 175) | HeaderCookie = "Cookie"
constant HeaderSetCookie (line 176) | HeaderSetCookie = "Set-Cookie"
constant HeaderIfModifiedSince (line 177) | HeaderIfModifiedSince = "If-Modified-Since"
constant HeaderLastModified (line 178) | HeaderLastModified = "Last-Modified"
constant HeaderLocation (line 179) | HeaderLocation = "Location"
constant HeaderRetryAfter (line 180) | HeaderRetryAfter = "Retry-After"
constant HeaderUpgrade (line 181) | HeaderUpgrade = "Upgrade"
constant HeaderVary (line 182) | HeaderVary = "Vary"
constant HeaderWWWAuthenticate (line 183) | HeaderWWWAuthenticate = "WWW-Authenticate"
constant HeaderXForwardedFor (line 184) | HeaderXForwardedFor = "X-Forwarded-For"
constant HeaderXForwardedProto (line 185) | HeaderXForwardedProto = "X-Forwarded-Proto"
constant HeaderXForwardedProtocol (line 186) | HeaderXForwardedProtocol = "X-Forwarded-Protocol"
constant HeaderXForwardedSsl (line 187) | HeaderXForwardedSsl = "X-Forwarded-Ssl"
constant HeaderXUrlScheme (line 188) | HeaderXUrlScheme = "X-Url-Scheme"
constant HeaderXHTTPMethodOverride (line 189) | HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
constant HeaderXRealIP (line 190) | HeaderXRealIP = "X-Real-Ip"
constant HeaderXRequestID (line 191) | HeaderXRequestID = "X-Request-Id"
constant HeaderXCorrelationID (line 192) | HeaderXCorrelationID = "X-Correlation-Id"
constant HeaderXRequestedWith (line 193) | HeaderXRequestedWith = "X-Requested-With"
constant HeaderServer (line 194) | HeaderServer = "Server"
constant HeaderOrigin (line 198) | HeaderOrigin = "Origin"
constant HeaderCacheControl (line 199) | HeaderCacheControl = "Cache-Control"
constant HeaderConnection (line 200) | HeaderConnection = "Connection"
constant HeaderAccessControlRequestMethod (line 203) | HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
constant HeaderAccessControlRequestHeaders (line 204) | HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
constant HeaderAccessControlAllowOrigin (line 205) | HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
constant HeaderAccessControlAllowMethods (line 206) | HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
constant HeaderAccessControlAllowHeaders (line 207) | HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
constant HeaderAccessControlAllowCredentials (line 208) | HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
constant HeaderAccessControlExposeHeaders (line 209) | HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
constant HeaderAccessControlMaxAge (line 210) | HeaderAccessControlMaxAge = "Access-Control-Max-Age"
constant HeaderStrictTransportSecurity (line 213) | HeaderStrictTransportSecurity = "Strict-Transport-Security"
constant HeaderXContentTypeOptions (line 214) | HeaderXContentTypeOptions = "X-Content-Type-Options"
constant HeaderXXSSProtection (line 215) | HeaderXXSSProtection = "X-XSS-Protection"
constant HeaderXFrameOptions (line 216) | HeaderXFrameOptions = "X-Frame-Options"
constant HeaderContentSecurityPolicy (line 217) | HeaderContentSecurityPolicy = "Content-Security-Policy"
constant HeaderContentSecurityPolicyReportOnly (line 218) | HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-...
constant HeaderXCSRFToken (line 219) | HeaderXCSRFToken = "X-CSRF-Token"
constant HeaderReferrerPolicy (line 220) | HeaderReferrerPolicy = "Referrer-Policy"
constant HeaderSecFetchSite (line 225) | HeaderSecFetchSite = "Sec-Fetch-Site"
type Config (line 229) | type Config struct
function NewWithConfig (line 286) | func NewWithConfig(config Config) *Echo {
function New (line 325) | func New() *Echo {
function DefaultHTTPErrorHandler (line 365) | func DefaultHTTPErrorHandler(exposeError bool) HTTPErrorHandler {
function StaticDirectoryHandler (line 550) | func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool...
function StaticFileHandler (line 584) | func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
function WrapHandler (line 734) | func WrapHandler(h http.Handler) HandlerFunc {
function WrapMiddleware (line 748) | func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
function applyMiddleware (line 767) | func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) Handle...
type defaultFS (line 779) | type defaultFS struct
method Open (line 792) | func (fs defaultFS) Open(name string) (fs.File, error) {
function newDefaultFS (line 784) | func newDefaultFS() *defaultFS {
function subFS (line 796) | func subFS(currentFs fs.FS, root string) (fs.FS, error) {
function MustSubFS (line 819) | func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS {
function sanitizeURI (line 827) | func sanitizeURI(uri string) string {
FILE: echo_test.go
type user (line 26) | type user struct
constant userJSON (line 32) | userJSON = `{"id":1,"name":"Jon Snow"}`
constant usersJSON (line 33) | usersJSON = `[{"id":1,"name":"Jon Snow"}]`
constant userXML (line 34) | userXML = `<user><id>1</id><name>Jon Snow</name></us...
constant userForm (line 35) | userForm = `id=1&name=Jon Snow`
constant invalidContent (line 36) | invalidContent = "invalid content"
constant userJSONInvalidType (line 37) | userJSONInvalidType = `{"id":"1","name":"Jon Snow"}`
constant userXMLConvertNumberError (line 38) | userXMLConvertNumberError = `<user><id>Number one</id><name>Jon Snow</...
constant userXMLUnsupportedTypeError (line 39) | userXMLUnsupportedTypeError = `<user><>Number one</><name>Jon Snow</name...
constant userJSONPretty (line 42) | userJSONPretty = `{
constant userXMLPretty (line 47) | userXMLPretty = `<user>
function TestEcho (line 54) | func TestEcho(t *testing.T) {
function TestNewWithConfig (line 68) | func TestNewWithConfig(t *testing.T) {
function TestEcho_StaticFS (line 82) | func TestEcho_StaticFS(t *testing.T) {
function TestEcho_FileFS (line 252) | func TestEcho_FileFS(t *testing.T) {
function TestEcho_StaticPanic (line 312) | func TestEcho_StaticPanic(t *testing.T) {
function TestEchoStaticRedirectIndex (line 339) | func TestEchoStaticRedirectIndex(t *testing.T) {
function TestEchoFile (line 362) | func TestEchoFile(t *testing.T) {
function TestEchoMiddleware (line 413) | func TestEchoMiddleware(t *testing.T) {
function TestEchoMiddlewareError (line 458) | func TestEchoMiddlewareError(t *testing.T) {
function TestEchoHandler (line 470) | func TestEchoHandler(t *testing.T) {
function TestEchoWrapHandler (line 483) | func TestEchoWrapHandler(t *testing.T) {
function TestEchoWrapMiddleware (line 505) | func TestEchoWrapMiddleware(t *testing.T) {
function TestEchoConnect (line 532) | func TestEchoConnect(t *testing.T) {
function TestEchoDelete (line 549) | func TestEchoDelete(t *testing.T) {
function TestEchoGet (line 566) | func TestEchoGet(t *testing.T) {
function TestEchoHead (line 583) | func TestEchoHead(t *testing.T) {
function TestEchoOptions (line 600) | func TestEchoOptions(t *testing.T) {
function TestEchoPatch (line 617) | func TestEchoPatch(t *testing.T) {
function TestEchoPost (line 634) | func TestEchoPost(t *testing.T) {
function TestEchoPut (line 651) | func TestEchoPut(t *testing.T) {
function TestEchoTrace (line 668) | func TestEchoTrace(t *testing.T) {
function TestEcho_Any (line 685) | func TestEcho_Any(t *testing.T) {
function TestEcho_Any_hasLowerPriority (line 702) | func TestEcho_Any_hasLowerPriority(t *testing.T) {
function TestEchoMatch (line 721) | func TestEchoMatch(t *testing.T) { // JFC
function TestEchoServeHTTPPathEncoding (line 729) | func TestEchoServeHTTPPathEncoding(t *testing.T) {
function TestEchoGroup (line 771) | func TestEchoGroup(t *testing.T) {
function TestEcho_RouteNotFound (line 829) | func TestEcho_RouteNotFound(t *testing.T) {
function TestEchoNotFound (line 893) | func TestEchoNotFound(t *testing.T) {
function TestEchoMethodNotAllowed (line 901) | func TestEchoMethodNotAllowed(t *testing.T) {
function TestEcho_OnAddRoute (line 915) | func TestEcho_OnAddRoute(t *testing.T) {
function TestEchoContext (line 982) | func TestEchoContext(t *testing.T) {
function TestPreMiddlewares (line 989) | func TestPreMiddlewares(t *testing.T) {
function TestMiddlewares (line 1002) | func TestMiddlewares(t *testing.T) {
function TestEcho_Start (line 1015) | func TestEcho_Start(t *testing.T) {
function request (line 1042) | func request(method, path string, e *Echo) (int, string) {
type customError (line 1049) | type customError struct
method StatusCode (line 1054) | func (ce *customError) StatusCode() int {
method MarshalJSON (line 1058) | func (ce *customError) MarshalJSON() ([]byte, error) {
method Error (line 1062) | func (ce *customError) Error() string {
function TestDefaultHTTPErrorHandler (line 1066) | func TestDefaultHTTPErrorHandler(t *testing.T) {
function TestDefaultHTTPErrorHandler_CommitedResponse (line 1170) | func TestDefaultHTTPErrorHandler_CommitedResponse(t *testing.T) {
function benchmarkEchoRoutes (line 1183) | func benchmarkEchoRoutes(b *testing.B, routes []testRoute) {
function BenchmarkEchoStaticRoutes (line 1209) | func BenchmarkEchoStaticRoutes(b *testing.B) {
function BenchmarkEchoStaticRoutesMisses (line 1213) | func BenchmarkEchoStaticRoutesMisses(b *testing.B) {
function BenchmarkEchoGitHubAPI (line 1217) | func BenchmarkEchoGitHubAPI(b *testing.B) {
function BenchmarkEchoGitHubAPIMisses (line 1221) | func BenchmarkEchoGitHubAPIMisses(b *testing.B) {
function BenchmarkEchoParseAPI (line 1225) | func BenchmarkEchoParseAPI(b *testing.B) {
FILE: echotest/context.go
type ContextConfig (line 20) | type ContextConfig struct
method ToContext (line 75) | func (conf ContextConfig) ToContext(t *testing.T) *echo.Context {
method ToContextRecorder (line 81) | func (conf ContextConfig) ToContextRecorder(t *testing.T) (*echo.Conte...
method ServeWithHandler (line 167) | func (conf ContextConfig) ServeWithHandler(t *testing.T, handler echo....
type MultipartForm (line 62) | type MultipartForm struct
type MultipartFormFile (line 68) | type MultipartFormFile struct
FILE: echotest/context_external_test.go
function TestToContext_JSONBody (line 12) | func TestToContext_JSONBody(t *testing.T) {
FILE: echotest/context_test.go
function TestServeWithHandler (line 13) | func TestServeWithHandler(t *testing.T) {
function TestServeWithHandler_error (line 27) | func TestServeWithHandler_error(t *testing.T) {
function TestToContext_QueryValues (line 43) | func TestToContext_QueryValues(t *testing.T) {
function TestToContext_Headers (line 55) | func TestToContext_Headers(t *testing.T) {
function TestToContext_PathValues (line 66) | func TestToContext_PathValues(t *testing.T) {
function TestToContext_RouteInfo (line 80) | func TestToContext_RouteInfo(t *testing.T) {
function TestToContext_FormValues (line 101) | func TestToContext_FormValues(t *testing.T) {
function TestToContext_MultipartForm (line 112) | func TestToContext_MultipartForm(t *testing.T) {
function TestToContext_JSONBody (line 141) | func TestToContext_JSONBody(t *testing.T) {
FILE: echotest/reader.go
type loadBytesOpts (line 13) | type loadBytesOpts
function TrimNewlineEnd (line 16) | func TrimNewlineEnd(bytes []byte) []byte {
function LoadBytes (line 26) | func LoadBytes(t *testing.T, name string, opts ...loadBytesOpts) []byte {
function loadBytes (line 36) | func loadBytes(t *testing.T, name string, callDepth int) []byte {
FILE: echotest/reader_external_test.go
constant testJSONContent (line 11) | testJSONContent = `{
function TestLoadBytesOK (line 15) | func TestLoadBytesOK(t *testing.T) {
function TestLoadBytes_custom (line 20) | func TestLoadBytes_custom(t *testing.T) {
FILE: echotest/reader_test.go
constant testJSONContent (line 9) | testJSONContent = `{
function TestLoadBytesOK (line 13) | func TestLoadBytesOK(t *testing.T) {
function TestLoadBytesOK_TrimNewlineEnd (line 18) | func TestLoadBytesOK_TrimNewlineEnd(t *testing.T) {
FILE: group.go
type Group (line 14) | type Group struct
method Use (line 22) | func (g *Group) Use(middleware ...MiddlewareFunc) {
method CONNECT (line 27) | func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFun...
method DELETE (line 32) | func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc...
method GET (line 37) | func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) R...
method HEAD (line 42) | func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) ...
method OPTIONS (line 47) | func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFun...
method PATCH (line 52) | func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc)...
method POST (line 57) | func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) ...
method PUT (line 62) | func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) R...
method TRACE (line 67) | func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc)...
method Any (line 72) | func (g *Group) Any(path string, handler HandlerFunc, middleware ...Mi...
method Match (line 77) | func (g *Group) Match(methods []string, path string, handler HandlerFu...
method Group (line 103) | func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg...
method Static (line 112) | func (g *Group) Static(pathPrefix, fsRoot string, middleware ...Middle...
method StaticFS (line 122) | func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS, middlewa...
method FileFS (line 132) | func (g *Group) FileFS(path, file string, filesystem fs.FS, m ...Middl...
method File (line 137) | func (g *Group) File(path, file string, middleware ...MiddlewareFunc) ...
method RouteNotFound (line 147) | func (g *Group) RouteNotFound(path string, h HandlerFunc, m ...Middlew...
method Add (line 152) | func (g *Group) Add(method, path string, handler HandlerFunc, middlewa...
method AddRoute (line 166) | func (g *Group) AddRoute(route Route) (RouteInfo, error) {
FILE: group_test.go
function TestGroup_withoutRouteWillNotExecuteMiddleware (line 17) | func TestGroup_withoutRouteWillNotExecuteMiddleware(t *testing.T) {
function TestGroup_withRoutesWillNotExecuteMiddlewareFor404 (line 37) | func TestGroup_withRoutesWillNotExecuteMiddlewareFor404(t *testing.T) {
function TestGroup_multiLevelGroup (line 59) | func TestGroup_multiLevelGroup(t *testing.T) {
function TestGroupFile (line 73) | func TestGroupFile(t *testing.T) {
function TestGroupRouteMiddleware (line 86) | func TestGroupRouteMiddleware(t *testing.T) {
function TestGroupRouteMiddlewareWithMatchAny (line 126) | func TestGroupRouteMiddlewareWithMatchAny(t *testing.T) {
function TestGroup_CONNECT (line 165) | func TestGroup_CONNECT(t *testing.T) {
function TestGroup_DELETE (line 183) | func TestGroup_DELETE(t *testing.T) {
function TestGroup_HEAD (line 201) | func TestGroup_HEAD(t *testing.T) {
function TestGroup_OPTIONS (line 219) | func TestGroup_OPTIONS(t *testing.T) {
function TestGroup_PATCH (line 237) | func TestGroup_PATCH(t *testing.T) {
function TestGroup_POST (line 255) | func TestGroup_POST(t *testing.T) {
function TestGroup_PUT (line 273) | func TestGroup_PUT(t *testing.T) {
function TestGroup_TRACE (line 291) | func TestGroup_TRACE(t *testing.T) {
function TestGroup_RouteNotFound (line 309) | func TestGroup_RouteNotFound(t *testing.T) {
function TestGroup_Any (line 374) | func TestGroup_Any(t *testing.T) {
function TestGroup_Match (line 392) | func TestGroup_Match(t *testing.T) {
function TestGroup_MatchWithErrors (line 409) | func TestGroup_MatchWithErrors(t *testing.T) {
function TestGroup_Static (line 449) | func TestGroup_Static(t *testing.T) {
function TestGroup_StaticMultiTest (line 468) | func TestGroup_StaticMultiTest(t *testing.T) {
function TestGroup_FileFS (line 652) | func TestGroup_FileFS(t *testing.T) {
function TestGroup_StaticPanic (line 713) | func TestGroup_StaticPanic(t *testing.T) {
function TestGroup_RouteNotFoundWithMiddleware (line 742) | func TestGroup_RouteNotFoundWithMiddleware(t *testing.T) {
FILE: httperror.go
type HTTPStatusCoder (line 39) | type HTTPStatusCoder interface
function StatusCode (line 45) | func StatusCode(err error) int {
function ResolveResponseStatus (line 66) | func ResolveResponseStatus(rw http.ResponseWriter, err error) (resp *Res...
function NewHTTPError (line 99) | func NewHTTPError(code int, message string) *HTTPError {
type HTTPError (line 107) | type HTTPError struct
method StatusCode (line 115) | func (he *HTTPError) StatusCode() int {
method Error (line 120) | func (he *HTTPError) Error() string {
method Wrap (line 132) | func (he HTTPError) Wrap(err error) error {
method Unwrap (line 140) | func (he *HTTPError) Unwrap() error {
type httpError (line 144) | type httpError struct
method StatusCode (line 148) | func (he httpError) StatusCode() int {
method Error (line 152) | func (he httpError) Error() string {
method Wrap (line 156) | func (he httpError) Wrap(err error) error {
FILE: httperror_external_test.go
function ExampleDefaultHTTPErrorHandler (line 15) | func ExampleDefaultHTTPErrorHandler() {
type apiError (line 34) | type apiError struct
method StatusCode (line 39) | func (e *apiError) StatusCode() int {
method MarshalJSON (line 43) | func (e *apiError) MarshalJSON() ([]byte, error) {
method Error (line 50) | func (e *apiError) Error() string {
FILE: httperror_test.go
function TestHTTPError_StatusCode (line 15) | func TestHTTPError_StatusCode(t *testing.T) {
function TestHTTPError_Error (line 26) | func TestHTTPError_Error(t *testing.T) {
function TestHTTPError_WrapUnwrap (line 50) | func TestHTTPError_WrapUnwrap(t *testing.T) {
function TestNewHTTPError (line 64) | func TestNewHTTPError(t *testing.T) {
function TestStatusCode (line 71) | func TestStatusCode(t *testing.T) {
function TestResolveResponseStatus (line 111) | func TestResolveResponseStatus(t *testing.T) {
FILE: ip.go
type ipChecker (line 136) | type ipChecker struct
method trust (line 182) | func (c *ipChecker) trust(ip net.IP) bool {
type TrustOption (line 144) | type TrustOption
function TrustLoopback (line 147) | func TrustLoopback(v bool) TrustOption {
function TrustLinkLocal (line 154) | func TrustLinkLocal(v bool) TrustOption {
function TrustPrivateNet (line 161) | func TrustPrivateNet(v bool) TrustOption {
function TrustIPRange (line 168) | func TrustIPRange(ipRange *net.IPNet) TrustOption {
function newIPChecker (line 174) | func newIPChecker(configs []TrustOption) *ipChecker {
type IPExtractor (line 203) | type IPExtractor
function ExtractIPDirect (line 207) | func ExtractIPDirect() IPExtractor {
function extractIP (line 211) | func extractIP(req *http.Request) string {
function ExtractIPFromRealIPHeader (line 224) | func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
function ExtractIPFromXFFHeader (line 242) | func ExtractIPFromXFFHeader(options ...TrustOption) IPExtractor {
FILE: ip_test.go
function mustParseCIDR (line 14) | func mustParseCIDR(s string) *net.IPNet {
function TestIPChecker_TrustOption (line 22) | func TestIPChecker_TrustOption(t *testing.T) {
function TestTrustIPRange (line 65) | func TestTrustIPRange(t *testing.T) {
function TestTrustPrivateNet (line 169) | func TestTrustPrivateNet(t *testing.T) {
function TestTrustLinkLocal (line 278) | func TestTrustLinkLocal(t *testing.T) {
function TestTrustLoopback (line 331) | func TestTrustLoopback(t *testing.T) {
function TestExtractIPDirect (line 369) | func TestExtractIPDirect(t *testing.T) {
function TestExtractIPFromRealIPHeader (line 492) | func TestExtractIPFromRealIPHeader(t *testing.T) {
function TestExtractIPFromXFFHeader (line 603) | func TestExtractIPFromXFFHeader(t *testing.T) {
FILE: json.go
type DefaultJSONSerializer (line 11) | type DefaultJSONSerializer struct
method Serialize (line 15) | func (d DefaultJSONSerializer) Serialize(c *Context, target any, inden...
method Deserialize (line 24) | func (d DefaultJSONSerializer) Deserialize(c *Context, target any) err...
FILE: json_test.go
function TestDefaultJSONCodec_Encode (line 16) | func TestDefaultJSONCodec_Encode(t *testing.T) {
function TestDefaultJSONCodec_Decode (line 53) | func TestDefaultJSONCodec_Decode(t *testing.T) {
FILE: middleware/basic_auth.go
type BasicAuthConfig (line 21) | type BasicAuthConfig struct
method ToMiddleware (line 97) | func (config BasicAuthConfig) ToMiddleware() (echo.MiddlewareFunc, err...
type BasicAuthValidator (line 76) | type BasicAuthValidator
constant basic (line 79) | basic = "basic"
constant defaultRealm (line 80) | defaultRealm = "Restricted"
function BasicAuth (line 87) | func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc {
function BasicAuthWithConfig (line 92) | func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
FILE: middleware/basic_auth_test.go
function TestBasicAuth (line 19) | func TestBasicAuth(t *testing.T) {
function TestBasicAuth_panic (line 159) | func TestBasicAuth_panic(t *testing.T) {
function TestBasicAuthWithConfig_panic (line 171) | func TestBasicAuthWithConfig_panic(t *testing.T) {
function TestBasicAuthRealm (line 183) | func TestBasicAuthRealm(t *testing.T) {
FILE: middleware/body_dump.go
type BodyDumpConfig (line 19) | type BodyDumpConfig struct
method ToMiddleware (line 73) | func (config BodyDumpConfig) ToMiddleware() (echo.MiddlewareFunc, erro...
type BodyDumpHandler (line 43) | type BodyDumpHandler
type bodyDumpResponseWriter (line 45) | type bodyDumpResponseWriter struct
method WriteHeader (line 146) | func (w *bodyDumpResponseWriter) WriteHeader(code int) {
method Write (line 150) | func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
method Flush (line 154) | func (w *bodyDumpResponseWriter) Flush() {
method Hijack (line 161) | func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter...
method Unwrap (line 165) | func (w *bodyDumpResponseWriter) Unwrap() http.ResponseWriter {
function BodyDump (line 58) | func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc {
function BodyDumpWithConfig (line 68) | func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc {
type limitedWriter (line 175) | type limitedWriter struct
method Write (line 182) | func (w *limitedWriter) Write(b []byte) (n int, err error) {
FILE: middleware/body_dump_test.go
function TestBodyDump (line 18) | func TestBodyDump(t *testing.T) {
function TestBodyDump_skipper (line 49) | func TestBodyDump_skipper(t *testing.T) {
function TestBodyDump_fails (line 75) | func TestBodyDump_fails(t *testing.T) {
function TestBodyDumpWithConfig_panic (line 94) | func TestBodyDumpWithConfig_panic(t *testing.T) {
function TestBodyDump_panic (line 109) | func TestBodyDump_panic(t *testing.T) {
function TestBodyDumpResponseWriter_CanNotFlush (line 120) | func TestBodyDumpResponseWriter_CanNotFlush(t *testing.T) {
function TestBodyDumpResponseWriter_CanFlush (line 129) | func TestBodyDumpResponseWriter_CanFlush(t *testing.T) {
function TestBodyDumpResponseWriter_CanUnwrap (line 138) | func TestBodyDumpResponseWriter_CanUnwrap(t *testing.T) {
function TestBodyDumpResponseWriter_CanHijack (line 147) | func TestBodyDumpResponseWriter_CanHijack(t *testing.T) {
function TestBodyDumpResponseWriter_CanNotHijack (line 156) | func TestBodyDumpResponseWriter_CanNotHijack(t *testing.T) {
function TestBodyDump_ReadError (line 165) | func TestBodyDump_ReadError(t *testing.T) {
type failingReadCloser (line 201) | type failingReadCloser struct
method Read (line 208) | func (f *failingReadCloser) Read(p []byte) (n int, err error) {
method Close (line 223) | func (f *failingReadCloser) Close() error {
function TestBodyDump_RequestWithinLimit (line 227) | func TestBodyDump_RequestWithinLimit(t *testing.T) {
function TestBodyDump_RequestExceedsLimit (line 255) | func TestBodyDump_RequestExceedsLimit(t *testing.T) {
function TestBodyDump_RequestAtExactLimit (line 287) | func TestBodyDump_RequestAtExactLimit(t *testing.T) {
function TestBodyDump_ResponseWithinLimit (line 316) | func TestBodyDump_ResponseWithinLimit(t *testing.T) {
function TestBodyDump_ResponseExceedsLimit (line 343) | func TestBodyDump_ResponseExceedsLimit(t *testing.T) {
function TestBodyDump_ClientGetsFullResponse (line 374) | func TestBodyDump_ClientGetsFullResponse(t *testing.T) {
function TestBodyDump_BothLimitsSimultaneous (line 406) | func TestBodyDump_BothLimitsSimultaneous(t *testing.T) {
function TestBodyDump_DefaultConfig (line 438) | func TestBodyDump_DefaultConfig(t *testing.T) {
function TestBodyDump_LargeRequestDosPrevention (line 464) | func TestBodyDump_LargeRequestDosPrevention(t *testing.T) {
function TestBodyDump_LargeResponseDosPrevention (line 496) | func TestBodyDump_LargeResponseDosPrevention(t *testing.T) {
function BenchmarkBodyDump_WithLimit (line 529) | func BenchmarkBodyDump_WithLimit(b *testing.B) {
function BenchmarkBodyDump_BufferPooling (line 557) | func BenchmarkBodyDump_BufferPooling(b *testing.B) {
FILE: middleware/body_limit.go
type BodyLimitConfig (line 15) | type BodyLimitConfig struct
method ToMiddleware (line 47) | func (config BodyLimitConfig) ToMiddleware() (echo.MiddlewareFunc, err...
type limitedReader (line 23) | type limitedReader struct
method Read (line 83) | func (r *limitedReader) Read(b []byte) (n int, err error) {
method Close (line 92) | func (r *limitedReader) Close() error {
method Reset (line 96) | func (r *limitedReader) Reset(reader io.ReadCloser) {
function BodyLimit (line 34) | func BodyLimit(limitBytes int64) echo.MiddlewareFunc {
function BodyLimitWithConfig (line 42) | func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
FILE: middleware/body_limit_test.go
function TestBodyLimitConfig_ToMiddleware (line 17) | func TestBodyLimitConfig_ToMiddleware(t *testing.T) {
function TestBodyLimitReader (line 71) | func TestBodyLimitReader(t *testing.T) {
function TestBodyLimit_skipper (line 96) | func TestBodyLimit_skipper(t *testing.T) {
function TestBodyLimitWithConfig (line 124) | func TestBodyLimitWithConfig(t *testing.T) {
function TestBodyLimit (line 146) | func TestBodyLimit(t *testing.T) {
FILE: middleware/compress.go
constant gzipScheme (line 21) | gzipScheme = "gzip"
type GzipConfig (line 25) | type GzipConfig struct
method ToMiddleware (line 69) | func (config GzipConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
type gzipResponseWriter (line 47) | type gzipResponseWriter struct
method WriteHeader (line 147) | func (w *gzipResponseWriter) WriteHeader(code int) {
method Write (line 156) | func (w *gzipResponseWriter) Write(b []byte) (int, error) {
method Flush (line 183) | func (w *gzipResponseWriter) Flush() {
method Hijack (line 201) | func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, er...
method Unwrap (line 205) | func (w *gzipResponseWriter) Unwrap() http.ResponseWriter {
method Push (line 209) | func (w *gzipResponseWriter) Push(target string, opts *http.PushOption...
function Gzip (line 59) | func Gzip() echo.MiddlewareFunc {
function GzipWithConfig (line 64) | func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
function gzipCompressPool (line 216) | func gzipCompressPool(config GzipConfig) sync.Pool {
function bufferPool (line 228) | func bufferPool() sync.Pool {
FILE: middleware/compress_test.go
function TestGzip_NoAcceptEncodingHeader (line 20) | func TestGzip_NoAcceptEncodingHeader(t *testing.T) {
function TestMustGzipWithConfig_panics (line 38) | func TestMustGzipWithConfig_panics(t *testing.T) {
function TestGzip_AcceptEncodingHeader (line 44) | func TestGzip_AcceptEncodingHeader(t *testing.T) {
function TestGzip_chunked (line 71) | func TestGzip_chunked(t *testing.T) {
function TestGzip_NoContent (line 131) | func TestGzip_NoContent(t *testing.T) {
function TestGzip_Empty (line 147) | func TestGzip_Empty(t *testing.T) {
function TestGzip_ErrorReturned (line 168) | func TestGzip_ErrorReturned(t *testing.T) {
function TestGzipWithConfig_invalidLevel (line 182) | func TestGzipWithConfig_invalidLevel(t *testing.T) {
function TestGzipWithStatic (line 189) | func TestGzipWithStatic(t *testing.T) {
function TestGzipWithMinLength (line 219) | func TestGzipWithMinLength(t *testing.T) {
function TestGzipWithMinLengthTooShort (line 242) | func TestGzipWithMinLengthTooShort(t *testing.T) {
function TestGzipWithResponseWithoutBody (line 258) | func TestGzipWithResponseWithoutBody(t *testing.T) {
function TestGzipWithMinLengthChunked (line 276) | func TestGzipWithMinLengthChunked(t *testing.T) {
function TestGzipWithMinLengthNoContent (line 335) | func TestGzipWithMinLengthNoContent(t *testing.T) {
function TestGzipResponseWriter_CanUnwrap (line 351) | func TestGzipResponseWriter_CanUnwrap(t *testing.T) {
function TestGzipResponseWriter_CanHijack (line 360) | func TestGzipResponseWriter_CanHijack(t *testing.T) {
function TestGzipResponseWriter_CanNotHijack (line 369) | func TestGzipResponseWriter_CanNotHijack(t *testing.T) {
function BenchmarkGzip (line 378) | func BenchmarkGzip(b *testing.B) {
FILE: middleware/context_timeout.go
type ContextTimeoutConfig (line 15) | type ContextTimeoutConfig struct
method ToMiddleware (line 38) | func (config ContextTimeoutConfig) ToMiddleware() (echo.MiddlewareFunc...
function ContextTimeout (line 28) | func ContextTimeout(timeout time.Duration) echo.MiddlewareFunc {
function ContextTimeoutWithConfig (line 33) | func ContextTimeoutWithConfig(config ContextTimeoutConfig) echo.Middlewa...
FILE: middleware/context_timeout_test.go
function TestContextTimeoutSkipper (line 20) | func TestContextTimeoutSkipper(t *testing.T) {
function TestContextTimeoutWithTimeout0 (line 47) | func TestContextTimeoutWithTimeout0(t *testing.T) {
function TestContextTimeoutErrorOutInHandler (line 54) | func TestContextTimeoutErrorOutInHandler(t *testing.T) {
function TestContextTimeoutSuccessfulRequest (line 81) | func TestContextTimeoutSuccessfulRequest(t *testing.T) {
function TestContextTimeoutTestRequestClone (line 103) | func TestContextTimeoutTestRequestClone(t *testing.T) {
function TestContextTimeoutWithDefaultErrorMessage (line 139) | func TestContextTimeoutWithDefaultErrorMessage(t *testing.T) {
function TestContextTimeoutCanHandleContextDeadlineOnNextHandler (line 167) | func TestContextTimeoutCanHandleContextDeadlineOnNextHandler(t *testing....
function sleepWithContext (line 216) | func sleepWithContext(ctx context.Context, d time.Duration) error {
FILE: middleware/cors.go
type CORSConfig (line 17) | type CORSConfig struct
method ToMiddleware (line 145) | func (config CORSConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
method starAllowOriginFunc (line 289) | func (config CORSConfig) starAllowOriginFunc(c *echo.Context, origin s...
method defaultAllowOriginFunc (line 293) | func (config CORSConfig) defaultAllowOriginFunc(c *echo.Context, origi...
function CORS (line 131) | func CORS(allowOrigins ...string) echo.MiddlewareFunc {
function CORSWithConfig (line 140) | func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
FILE: middleware/cors_test.go
function TestCORS (line 18) | func TestCORS(t *testing.T) {
function TestCORSConfig (line 36) | func TestCORSConfig(t *testing.T) {
function Test_allowOriginScheme (line 297) | func Test_allowOriginScheme(t *testing.T) {
function TestCORSWithConfig_AllowMethods (line 344) | func TestCORSWithConfig_AllowMethods(t *testing.T) {
function TestCorsHeaders (line 431) | func TestCorsHeaders(t *testing.T) {
function Test_allowOriginFunc (line 584) | func Test_allowOriginFunc(t *testing.T) {
FILE: middleware/csrf.go
constant CSRFUsingSecFetchSite (line 23) | CSRFUsingSecFetchSite = "_echo_csrf_using_sec_fetch_site_"
type CSRFConfig (line 26) | type CSRFConfig struct
method ToMiddleware (line 126) | func (config CSRFConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
method checkSecFetchSiteRequest (line 260) | func (config CSRFConfig) checkSecFetchSiteRequest(c *echo.Context) (bo...
function CSRF (line 116) | func CSRF() echo.MiddlewareFunc {
function CSRFWithConfig (line 121) | func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
function validateCSRFToken (line 254) | func validateCSRFToken(token, clientToken string) bool {
FILE: middleware/csrf_test.go
function TestCSRF_tokenExtractors (line 18) | func TestCSRF_tokenExtractors(t *testing.T) {
function TestCSRFWithConfig (line 230) | func TestCSRFWithConfig(t *testing.T) {
function TestCSRF (line 370) | func TestCSRF(t *testing.T) {
function TestCSRFSetSameSiteMode (line 386) | func TestCSRFSetSameSiteMode(t *testing.T) {
function TestCSRFWithoutSameSiteMode (line 405) | func TestCSRFWithoutSameSiteMode(t *testing.T) {
function TestCSRFWithSameSiteDefaultMode (line 422) | func TestCSRFWithSameSiteDefaultMode(t *testing.T) {
function TestCSRFWithSameSiteModeNone (line 441) | func TestCSRFWithSameSiteModeNone(t *testing.T) {
function TestCSRFConfig_skipper (line 462) | func TestCSRFConfig_skipper(t *testing.T) {
function TestCSRFErrorHandling (line 505) | func TestCSRFErrorHandling(t *testing.T) {
function TestCSRFConfig_checkSecFetchSiteRequest (line 527) | func TestCSRFConfig_checkSecFetchSiteRequest(t *testing.T) {
FILE: middleware/decompress.go
type DecompressConfig (line 16) | type DecompressConfig struct
method ToMiddleware (line 65) | func (config DecompressConfig) ToMiddleware() (echo.MiddlewareFunc, er...
constant GZIPEncoding (line 32) | GZIPEncoding string = "gzip"
type Decompressor (line 35) | type Decompressor interface
type DefaultGzipDecompressPool (line 40) | type DefaultGzipDecompressPool struct
method gzipDecompressPool (line 43) | func (d *DefaultGzipDecompressPool) gzipDecompressPool() sync.Pool {
function Decompress (line 52) | func Decompress() echo.MiddlewareFunc {
function DecompressWithConfig (line 60) | func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc {
type limitedGzipReader (line 130) | type limitedGzipReader struct
method Read (line 136) | func (r *limitedGzipReader) Read(p []byte) (n int, err error) {
method Close (line 153) | func (r *limitedGzipReader) Close() error {
FILE: middleware/decompress_test.go
function TestDecompress (line 21) | func TestDecompress(t *testing.T) {
function TestDecompress_skippedIfNoHeader (line 46) | func TestDecompress_skippedIfNoHeader(t *testing.T) {
function TestDecompressWithConfig_DefaultConfig_noDecode (line 64) | func TestDecompressWithConfig_DefaultConfig_noDecode(t *testing.T) {
function TestDecompressWithConfig_DefaultConfig (line 83) | func TestDecompressWithConfig_DefaultConfig(t *testing.T) {
function TestCompressRequestWithoutDecompressMiddleware (line 108) | func TestCompressRequestWithoutDecompressMiddleware(t *testing.T) {
function TestDecompressNoContent (line 126) | func TestDecompressNoContent(t *testing.T) {
function TestDecompressErrorReturned (line 145) | func TestDecompressErrorReturned(t *testing.T) {
function TestDecompressSkipper (line 161) | func TestDecompressSkipper(t *testing.T) {
type TestDecompressPoolWithError (line 182) | type TestDecompressPoolWithError struct
method gzipDecompressPool (line 185) | func (d *TestDecompressPoolWithError) gzipDecompressPool() sync.Pool {
function TestDecompressPoolError (line 193) | func TestDecompressPoolError(t *testing.T) {
function BenchmarkDecompress (line 214) | func BenchmarkDecompress(b *testing.B) {
function gzipString (line 237) | func gzipString(body string) ([]byte, error) {
function TestDecompress_WithinLimit (line 253) | func TestDecompress_WithinLimit(t *testing.T) {
function TestDecompress_ExceedsLimit (line 275) | func TestDecompress_ExceedsLimit(t *testing.T) {
function TestDecompress_AtExactLimit (line 301) | func TestDecompress_AtExactLimit(t *testing.T) {
function TestDecompress_ZipBomb (line 323) | func TestDecompress_ZipBomb(t *testing.T) {
function TestDecompress_UnlimitedExplicit (line 353) | func TestDecompress_UnlimitedExplicit(t *testing.T) {
function TestDecompress_DefaultLimit (line 375) | func TestDecompress_DefaultLimit(t *testing.T) {
function TestDecompress_SmallCustomLimit (line 398) | func TestDecompress_SmallCustomLimit(t *testing.T) {
function TestDecompress_MultipleReads (line 420) | func TestDecompress_MultipleReads(t *testing.T) {
function TestDecompress_LargePayloadDosPrevention (line 457) | func TestDecompress_LargePayloadDosPrevention(t *testing.T) {
function BenchmarkDecompress_WithLimit (line 487) | func BenchmarkDecompress_WithLimit(b *testing.B) {
FILE: middleware/extractor.go
constant extractorLimit (line 17) | extractorLimit = 20
type ExtractorSource (line 21) | type ExtractorSource
constant ExtractorSourceHeader (line 25) | ExtractorSourceHeader ExtractorSource = "header"
constant ExtractorSourceQuery (line 27) | ExtractorSourceQuery ExtractorSource = "query"
constant ExtractorSourcePathParam (line 29) | ExtractorSourcePathParam ExtractorSource = "param"
constant ExtractorSourceCookie (line 31) | ExtractorSourceCookie ExtractorSource = "cookie"
constant ExtractorSourceForm (line 33) | ExtractorSourceForm ExtractorSource = "form"
type ValueExtractorError (line 37) | type ValueExtractorError struct
method Error (line 42) | func (e *ValueExtractorError) Error() string {
type ValuesExtractor (line 54) | type ValuesExtractor
function CreateExtractors (line 74) | func CreateExtractors(lookups string, limit uint) ([]ValuesExtractor, er...
function createExtractors (line 78) | func createExtractors(lookups string, limit uint) ([]ValuesExtractor, er...
function valuesFromHeader (line 122) | func valuesFromHeader(header string, valuePrefix string, limit uint) Val...
function valuesFromQuery (line 164) | func valuesFromQuery(param string, limit uint) ValuesExtractor {
function valuesFromParam (line 180) | func valuesFromParam(param string, limit uint) ValuesExtractor {
function valuesFromCookie (line 205) | func valuesFromCookie(name string, limit uint) ValuesExtractor {
function valuesFromForm (line 235) | func valuesFromForm(name string, limit uint) ValuesExtractor {
FILE: middleware/extractor_test.go
function TestCreateExtractors (line 20) | func TestCreateExtractors(t *testing.T) {
function TestValuesFromHeader (line 129) | func TestValuesFromHeader(t *testing.T) {
function TestValuesFromQuery (line 259) | func TestValuesFromQuery(t *testing.T) {
function TestValuesFromParam (line 324) | func TestValuesFromParam(t *testing.T) {
function TestValuesFromCookie (line 407) | func TestValuesFromCookie(t *testing.T) {
function TestValuesFromForm (line 491) | func TestValuesFromForm(t *testing.T) {
FILE: middleware/key_auth.go
type KeyAuthConfig (line 19) | type KeyAuthConfig struct
method ToMiddleware (line 138) | func (config KeyAuthConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
type KeyAuthValidator (line 100) | type KeyAuthValidator
type KeyAuthErrorHandler (line 103) | type KeyAuthErrorHandler
function KeyAuth (line 122) | func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc {
function KeyAuthWithConfig (line 133) | func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
FILE: middleware/key_auth_test.go
function testKeyValidator (line 18) | func testKeyValidator(c *echo.Context, key string, source ExtractorSourc...
function TestKeyAuth (line 32) | func TestKeyAuth(t *testing.T) {
function TestKeyAuthWithConfig (line 52) | func TestKeyAuthWithConfig(t *testing.T) {
function TestKeyAuthWithConfig_errors (line 283) | func TestKeyAuthWithConfig_errors(t *testing.T) {
function TestMustKeyAuthWithConfig_panic (line 340) | func TestMustKeyAuthWithConfig_panic(t *testing.T) {
function TestKeyAuth_errorHandlerSwallowsError (line 346) | func TestKeyAuth_errorHandlerSwallowsError(t *testing.T) {
FILE: middleware/method_override.go
type MethodOverrideConfig (line 13) | type MethodOverrideConfig struct
method ToMiddleware (line 46) | func (config MethodOverrideConfig) ToMiddleware() (echo.MiddlewareFunc...
type MethodOverrideGetter (line 23) | type MethodOverrideGetter
function MethodOverride (line 36) | func MethodOverride() echo.MiddlewareFunc {
function MethodOverrideWithConfig (line 41) | func MethodOverrideWithConfig(config MethodOverrideConfig) echo.Middlewa...
function MethodFromHeader (line 75) | func MethodFromHeader(header string) MethodOverrideGetter {
function MethodFromForm (line 83) | func MethodFromForm(param string) MethodOverrideGetter {
function MethodFromQuery (line 91) | func MethodFromQuery(param string) MethodOverrideGetter {
FILE: middleware/method_override_test.go
function TestMethodOverride (line 16) | func TestMethodOverride(t *testing.T) {
function TestMethodOverride_formParam (line 36) | func TestMethodOverride_formParam(t *testing.T) {
function TestMethodOverride_queryParam (line 56) | func TestMethodOverride_queryParam(t *testing.T) {
function TestMethodOverride_ignoreGet (line 75) | func TestMethodOverride_ignoreGet(t *testing.T) {
FILE: middleware/middleware.go
type Skipper (line 16) | type Skipper
type BeforeFunc (line 19) | type BeforeFunc
function captureTokens (line 21) | func captureTokens(pattern *regexp.Regexp, input string) *strings.Replac...
function rewriteRulesRegex (line 36) | func rewriteRulesRegex(rewrite map[string]string) map[*regexp.Regexp]str...
function rewriteURL (line 51) | func rewriteURL(rewriteRegex map[*regexp.Regexp]string, req *http.Reques...
function DefaultSkipper (line 87) | func DefaultSkipper(c *echo.Context) bool {
function toMiddlewareOrPanic (line 91) | func toMiddlewareOrPanic(config echo.MiddlewareConfigurator) echo.Middle...
FILE: middleware/middleware_test.go
function TestRewriteURL (line 17) | func TestRewriteURL(t *testing.T) {
type testResponseWriterNoFlushHijack (line 100) | type testResponseWriterNoFlushHijack struct
method WriteHeader (line 103) | func (w *testResponseWriterNoFlushHijack) WriteHeader(statusCode int) {
method Write (line 105) | func (w *testResponseWriterNoFlushHijack) Write([]byte) (int, error) {
method Header (line 108) | func (w *testResponseWriterNoFlushHijack) Header() http.Header {
type testResponseWriterUnwrapper (line 112) | type testResponseWriterUnwrapper struct
method WriteHeader (line 117) | func (w *testResponseWriterUnwrapper) WriteHeader(statusCode int) {
method Write (line 119) | func (w *testResponseWriterUnwrapper) Write([]byte) (int, error) {
method Header (line 122) | func (w *testResponseWriterUnwrapper) Header() http.Header {
method Unwrap (line 125) | func (w *testResponseWriterUnwrapper) Unwrap() http.ResponseWriter {
type testResponseWriterUnwrapperHijack (line 130) | type testResponseWriterUnwrapperHijack struct
method Hijack (line 134) | func (w *testResponseWriterUnwrapperHijack) Hijack() (net.Conn, *bufio...
FILE: middleware/proxy.go
type ProxyConfig (line 28) | type ProxyConfig struct
method ToMiddleware (line 305) | func (config ProxyConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
type ProxyTarget (line 92) | type ProxyTarget struct
type ProxyBalancer (line 99) | type ProxyBalancer interface
type commonBalancer (line 105) | type commonBalancer struct
method AddTarget (line 207) | func (b *commonBalancer) AddTarget(target *ProxyTarget) bool {
method RemoveTarget (line 222) | func (b *commonBalancer) RemoveTarget(name string) bool {
type randomBalancer (line 111) | type randomBalancer struct
method Next (line 237) | func (b *randomBalancer) Next(c *echo.Context) (*ProxyTarget, error) {
type roundRobinBalancer (line 117) | type roundRobinBalancer struct
method Next (line 256) | func (b *roundRobinBalancer) Next(c *echo.Context) (*ProxyTarget, erro...
function proxyRaw (line 129) | func proxyRaw(c *echo.Context, t *ProxyTarget, config ProxyConfig) http....
function NewRandomBalancer (line 188) | func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer {
function NewRoundRobinBalancer (line 198) | func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer {
function Proxy (line 291) | func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {
function ProxyWithConfig (line 300) | func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
constant StatusCodeContextCanceled (line 412) | StatusCodeContextCanceled = 499
function proxyHTTP (line 414) | func proxyHTTP(c *echo.Context, tgt *ProxyTarget, config ProxyConfig) ht...
FILE: middleware/proxy_test.go
function TestProxy (line 28) | func TestProxy(t *testing.T) {
function TestMustProxyWithConfig_emptyBalancerPanics (line 130) | func TestMustProxyWithConfig_emptyBalancerPanics(t *testing.T) {
function TestProxyRealIPHeader (line 136) | func TestProxyRealIPHeader(t *testing.T) {
function TestProxyRewrite (line 179) | func TestProxyRewrite(t *testing.T) {
function TestProxyRewriteRegex (line 260) | func TestProxyRewriteRegex(t *testing.T) {
function TestProxyError (line 320) | func TestProxyError(t *testing.T) {
function TestClientCancelConnectionResultsHTTPCode499 (line 359) | func TestClientCancelConnectionResultsHTTPCode499(t *testing.T) {
type testProvider (line 389) | type testProvider struct
method Next (line 395) | func (p *testProvider) Next(c *echo.Context) (*ProxyTarget, error) {
function TestTargetProvider (line 399) | func TestTargetProvider(t *testing.T) {
function TestFailNextTarget (line 417) | func TestFailNextTarget(t *testing.T) {
function TestRandomBalancerWithNoTargets (line 434) | func TestRandomBalancerWithNoTargets(t *testing.T) {
function TestRoundRobinBalancerWithNoTargets (line 447) | func TestRoundRobinBalancerWithNoTargets(t *testing.T) {
function TestProxyRetries (line 460) | func TestProxyRetries(t *testing.T) {
function TestProxyRetryWithBackendTimeout (line 638) | func TestProxyRetryWithBackendTimeout(t *testing.T) {
function TestProxyErrorHandler (line 694) | func TestProxyErrorHandler(t *testing.T) {
type testContextKey (line 775) | type testContextKey
type customBalancer (line 776) | type customBalancer struct
method AddTarget (line 780) | func (b *customBalancer) AddTarget(target *ProxyTarget) bool {
method RemoveTarget (line 783) | func (b *customBalancer) RemoveTarget(name string) bool {
method Next (line 787) | func (b *customBalancer) Next(c *echo.Context) (*ProxyTarget, error) {
function TestModifyResponseUseContext (line 793) | func TestModifyResponseUseContext(t *testing.T) {
function createSimpleWebSocketServer (line 829) | func createSimpleWebSocketServer(serveTLS bool) *httptest.Server {
function createSimpleProxyServer (line 851) | func createSimpleProxyServer(t *testing.T, srv *httptest.Server, serveTL...
function TestProxyWithConfigWebSocketNonTLS2NonTLS (line 887) | func TestProxyWithConfigWebSocketNonTLS2NonTLS(t *testing.T) {
function TestProxyWithConfigWebSocketTLS2TLS (line 928) | func TestProxyWithConfigWebSocketTLS2TLS(t *testing.T) {
function TestProxyWithConfigWebSocketNonTLS2TLS (line 972) | func TestProxyWithConfigWebSocketNonTLS2TLS(t *testing.T) {
function TestProxyWithConfigWebSocketTLS2NonTLS (line 1013) | func TestProxyWithConfigWebSocketTLS2NonTLS(t *testing.T) {
FILE: middleware/rate_limiter.go
type RateLimiterStore (line 18) | type RateLimiterStore interface
type RateLimiterConfig (line 23) | type RateLimiterConfig struct
method ToMiddleware (line 109) | func (config RateLimiterConfig) ToMiddleware() (echo.MiddlewareFunc, e...
type Extractor (line 37) | type Extractor
function RateLimiter (line 71) | func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc {
function RateLimiterWithConfig (line 104) | func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc {
type RateLimiterMemoryStore (line 148) | type RateLimiterMemoryStore struct
method Allow (line 234) | func (store *RateLimiterMemoryStore) Allow(identifier string) (bool, e...
method cleanupStaleVisitors (line 256) | func (store *RateLimiterMemoryStore) cleanupStaleVisitors(now time.Tim...
type Visitor (line 160) | type Visitor struct
function NewRateLimiterMemoryStore (line 178) | func NewRateLimiterMemoryStore(rateLimit float64) (store *RateLimiterMem...
function NewRateLimiterMemoryStoreWithConfig (line 203) | func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreCo...
type RateLimiterMemoryStoreConfig (line 222) | type RateLimiterMemoryStoreConfig struct
FILE: middleware/rate_limiter_test.go
function TestRateLimiter (line 21) | func TestRateLimiter(t *testing.T) {
function TestMustRateLimiterWithConfig_panicBehaviour (line 62) | func TestMustRateLimiterWithConfig_panicBehaviour(t *testing.T) {
function TestRateLimiterWithConfig (line 74) | func TestRateLimiterWithConfig(t *testing.T) {
function TestRateLimiterWithConfig_defaultDenyHandler (line 129) | func TestRateLimiterWithConfig_defaultDenyHandler(t *testing.T) {
function TestRateLimiterWithConfig_defaultConfig (line 181) | func TestRateLimiterWithConfig_defaultConfig(t *testing.T) {
function TestRateLimiterWithConfig_skipper (line 228) | func TestRateLimiterWithConfig_skipper(t *testing.T) {
function TestRateLimiterWithConfig_skipperNoSkip (line 264) | func TestRateLimiterWithConfig_skipperNoSkip(t *testing.T) {
function TestRateLimiterWithConfig_beforeFunc (line 299) | func TestRateLimiterWithConfig_beforeFunc(t *testing.T) {
function TestRateLimiterMemoryStore_Allow (line 333) | func TestRateLimiterMemoryStore_Allow(t *testing.T) {
function TestRateLimiterMemoryStore_cleanupStaleVisitors (line 375) | func TestRateLimiterMemoryStore_cleanupStaleVisitors(t *testing.T) {
function TestNewRateLimiterMemoryStore (line 414) | func TestNewRateLimiterMemoryStore(t *testing.T) {
function TestRateLimiterMemoryStore_FractionalRateDefaultBurst (line 435) | func TestRateLimiterMemoryStore_FractionalRateDefaultBurst(t *testing.T) {
function generateAddressList (line 462) | func generateAddressList(count int) []string {
function run (line 470) | func run(wg *sync.WaitGroup, store RateLimiterStore, addrs []string, max...
function benchmarkStore (line 477) | func benchmarkStore(store RateLimiterStore, parallel int, max int, b *te...
constant testExpiresIn (line 488) | testExpiresIn = 1000 * time.Millisecond
function BenchmarkRateLimiterMemoryStore_1000 (line 491) | func BenchmarkRateLimiterMemoryStore_1000(b *testing.B) {
function BenchmarkRateLimiterMemoryStore_10000 (line 496) | func BenchmarkRateLimiterMemoryStore_10000(b *testing.B) {
function BenchmarkRateLimiterMemoryStore_100000 (line 501) | func BenchmarkRateLimiterMemoryStore_100000(b *testing.B) {
function BenchmarkRateLimiterMemoryStore_conc100_10000 (line 506) | func BenchmarkRateLimiterMemoryStore_conc100_10000(b *testing.B) {
function TestRateLimiterMemoryStore_TOCTOUFix (line 513) | func TestRateLimiterMemoryStore_TOCTOUFix(t *testing.T) {
function TestRateLimiterMemoryStore_ConcurrentAccess (line 541) | func TestRateLimiterMemoryStore_ConcurrentAccess(t *testing.T) {
function TestRateLimiterMemoryStore_RaceDetection (line 586) | func TestRateLimiterMemoryStore_RaceDetection(t *testing.T) {
function TestRateLimiterMemoryStore_TimeOrdering (line 617) | func TestRateLimiterMemoryStore_TimeOrdering(t *testing.T) {
FILE: middleware/recover.go
type RecoverConfig (line 15) | type RecoverConfig struct
method ToMiddleware (line 53) | func (config RecoverConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
function Recover (line 43) | func Recover() echo.MiddlewareFunc {
function RecoverWithConfig (line 48) | func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
type PanicStackError (line 92) | type PanicStackError struct
method Error (line 97) | func (e *PanicStackError) Error() string {
method Unwrap (line 101) | func (e *PanicStackError) Unwrap() error {
FILE: middleware/recover_test.go
function TestRecover (line 18) | func TestRecover(t *testing.T) {
function TestRecover_skipper (line 42) | func TestRecover_skipper(t *testing.T) {
function TestRecoverErrAbortHandler (line 67) | func TestRecoverErrAbortHandler(t *testing.T) {
function TestRecoverWithConfig (line 94) | func TestRecoverWithConfig(t *testing.T) {
FILE: middleware/redirect.go
type RedirectConfig (line 15) | type RedirectConfig struct
method ToMiddleware (line 119) | func (config RedirectConfig) ToMiddleware() (echo.MiddlewareFunc, erro...
type redirectLogic (line 29) | type redirectLogic
constant www (line 31) | www = "www."
function HTTPSRedirect (line 52) | func HTTPSRedirect() echo.MiddlewareFunc {
function HTTPSRedirectWithConfig (line 57) | func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
function HTTPSWWWRedirect (line 66) | func HTTPSWWWRedirect() echo.MiddlewareFunc {
function HTTPSWWWRedirectWithConfig (line 71) | func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFu...
function HTTPSNonWWWRedirect (line 80) | func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
function HTTPSNonWWWRedirectWithConfig (line 85) | func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.Middlewar...
function WWWRedirect (line 94) | func WWWRedirect() echo.MiddlewareFunc {
function WWWRedirectWithConfig (line 99) | func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
function NonWWWRedirect (line 108) | func NonWWWRedirect() echo.MiddlewareFunc {
function NonWWWRedirectWithConfig (line 113) | func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
FILE: middleware/redirect_test.go
type middlewareGenerator (line 15) | type middlewareGenerator
function TestRedirectHTTPSRedirect (line 17) | func TestRedirectHTTPSRedirect(t *testing.T) {
function TestRedirectHTTPSWWWRedirect (line 47) | func TestRedirectHTTPSWWWRedirect(t *testing.T) {
function TestRedirectHTTPSNonWWWRedirect (line 98) | func TestRedirectHTTPSNonWWWRedirect(t *testing.T) {
function TestRedirectWWWRedirect (line 144) | func TestRedirectWWWRedirect(t *testing.T) {
function TestRedirectNonWWWRedirect (line 189) | func TestRedirectNonWWWRedirect(t *testing.T) {
function TestNonWWWRedirectWithConfig (line 229) | func TestNonWWWRedirectWithConfig(t *testing.T) {
function redirectTest (line 279) | func redirectTest(fn middlewareGenerator, host string, header http.Heade...
FILE: middleware/request_id.go
type RequestIDConfig (line 11) | type RequestIDConfig struct
method ToMiddleware (line 42) | func (config RequestIDConfig) ToMiddleware() (echo.MiddlewareFunc, err...
function RequestID (line 30) | func RequestID() echo.MiddlewareFunc {
function RequestIDWithConfig (line 37) | func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc {
FILE: middleware/request_id_test.go
function TestRequestID (line 15) | func TestRequestID(t *testing.T) {
function TestMustRequestIDWithConfig_skipper (line 31) | func TestMustRequestIDWithConfig_skipper(t *testing.T) {
function TestMustRequestIDWithConfig_customGenerator (line 59) | func TestMustRequestIDWithConfig_customGenerator(t *testing.T) {
function TestMustRequestIDWithConfig_RequestIDHandler (line 77) | func TestMustRequestIDWithConfig_RequestIDHandler(t *testing.T) {
function TestRequestIDWithConfig (line 100) | func TestRequestIDWithConfig(t *testing.T) {
function TestRequestID_IDNotAltered (line 124) | func TestRequestID_IDNotAltered(t *testing.T) {
function TestRequestIDConfigDifferentHeader (line 141) | func TestRequestIDConfigDifferentHeader(t *testing.T) {
FILE: middleware/request_logger.go
type RequestLoggerConfig (line 124) | type RequestLoggerConfig struct
method ToMiddleware (line 246) | func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc,...
type RequestLoggerValues (line 189) | type RequestLoggerValues struct
function RequestLoggerWithConfig (line 237) | func RequestLoggerWithConfig(config RequestLoggerConfig) echo.Middleware...
function RequestLogger (line 395) | func RequestLogger() echo.MiddlewareFunc {
FILE: middleware/request_logger_test.go
function TestRequestLoggerOK (line 23) | func TestRequestLoggerOK(t *testing.T) {
function TestRequestLoggerError (line 72) | func TestRequestLoggerError(t *testing.T) {
function TestRequestLoggerWithConfig (line 115) | func TestRequestLoggerWithConfig(t *testing.T) {
function TestRequestLoggerWithConfig_missingOnLogValuesPanics (line 141) | func TestRequestLoggerWithConfig_missingOnLogValuesPanics(t *testing.T) {
function TestRequestLogger_skipper (line 149) | func TestRequestLogger_skipper(t *testing.T) {
function TestRequestLogger_beforeNextFunc (line 176) | func TestRequestLogger_beforeNextFunc(t *testing.T) {
function TestRequestLogger_logError (line 203) | func TestRequestLogger_logError(t *testing.T) {
function TestRequestLogger_HandleError (line 229) | func TestRequestLogger_HandleError(t *testing.T) {
function TestRequestLogger_LogValuesFuncError (line 272) | func TestRequestLogger_LogValuesFuncError(t *testing.T) {
function TestRequestLogger_ID (line 300) | func TestRequestLogger_ID(t *testing.T) {
function TestRequestLogger_headerIsCaseInsensitive (line 350) | func TestRequestLogger_headerIsCaseInsensitive(t *testing.T) {
function TestRequestLogger_allFields (line 379) | func TestRequestLogger_allFields(t *testing.T) {
function TestTestRequestLogger (line 468) | func TestTestRequestLogger(t *testing.T) {
function BenchmarkRequestLogger_withoutMapFields (line 542) | func BenchmarkRequestLogger_withoutMapFields(b *testing.B) {
function BenchmarkRequestLogger_withMapFields (line 583) | func BenchmarkRequestLogger_withMapFields(b *testing.B) {
FILE: middleware/rewrite.go
type RewriteConfig (line 14) | type RewriteConfig struct
method ToMiddleware (line 53) | func (config RewriteConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
function Rewrite (line 39) | func Rewrite(rules map[string]string) echo.MiddlewareFunc {
function RewriteWithConfig (line 48) | func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
FILE: middleware/rewrite_test.go
function TestRewriteAfterRouting (line 18) | func TestRewriteAfterRouting(t *testing.T) {
function TestMustRewriteWithConfig_emptyRulesPanics (line 96) | func TestMustRewriteWithConfig_emptyRulesPanics(t *testing.T) {
function TestMustRewriteWithConfig_skipper (line 102) | func TestMustRewriteWithConfig_skipper(t *testing.T) {
function TestEchoRewritePreMiddleware (line 153) | func TestEchoRewritePreMiddleware(t *testing.T) {
function TestRewriteWithConfigPreMiddleware_Issue1143 (line 175) | func TestRewriteWithConfigPreMiddleware_Issue1143(t *testing.T) {
function TestEchoRewriteWithCaret (line 207) | func TestEchoRewriteWithCaret(t *testing.T) {
function TestEchoRewriteWithRegexRules (line 234) | func TestEchoRewriteWithRegexRules(t *testing.T) {
function TestEchoRewriteReplacementEscaping (line 276) | func TestEchoRewriteReplacementEscaping(t *testing.T) {
FILE: middleware/secure.go
type SecureConfig (line 13) | type SecureConfig struct
method ToMiddleware (line 101) | func (config SecureConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
function Secure (line 91) | func Secure() echo.MiddlewareFunc {
function SecureWithConfig (line 96) | func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
FILE: middleware/secure_test.go
function TestSecure (line 15) | func TestSecure(t *testing.T) {
function TestSecureWithConfig (line 36) | func TestSecureWithConfig(t *testing.T) {
function TestSecureWithConfig_CSPReportOnly (line 69) | func TestSecureWithConfig_CSPReportOnly(t *testing.T) {
function TestSecureWithConfig_HSTSPreloadEnabled (line 101) | func TestSecureWithConfig_HSTSPreloadEnabled(t *testing.T) {
function TestSecureWithConfig_HSTSExcludeSubdomains (line 124) | func TestSecureWithConfig_HSTSExcludeSubdomains(t *testing.T) {
FILE: middleware/slash.go
type AddTrailingSlashConfig (line 15) | type AddTrailingSlashConfig struct
method ToMiddleware (line 39) | func (config AddTrailingSlashConfig) ToMiddleware() (echo.MiddlewareFu...
function AddTrailingSlash (line 29) | func AddTrailingSlash() echo.MiddlewareFunc {
function AddTrailingSlashWithConfig (line 34) | func AddTrailingSlashWithConfig(config AddTrailingSlashConfig) echo.Midd...
type RemoveTrailingSlashConfig (line 80) | type RemoveTrailingSlashConfig struct
method ToMiddleware (line 103) | func (config RemoveTrailingSlashConfig) ToMiddleware() (echo.Middlewar...
function RemoveTrailingSlash (line 93) | func RemoveTrailingSlash() echo.MiddlewareFunc {
function RemoveTrailingSlashWithConfig (line 98) | func RemoveTrailingSlashWithConfig(config RemoveTrailingSlashConfig) ech...
function sanitizeURI (line 144) | func sanitizeURI(uri string) string {
FILE: middleware/slash_test.go
function TestAddTrailingSlashWithConfig (line 15) | func TestAddTrailingSlashWithConfig(t *testing.T) {
function TestAddTrailingSlash (line 98) | func TestAddTrailingSlash(t *testing.T) {
function TestRemoveTrailingSlashWithConfig (line 144) | func TestRemoveTrailingSlashWithConfig(t *testing.T) {
function TestRemoveTrailingSlash (line 234) | func TestRemoveTrailingSlash(t *testing.T) {
FILE: middleware/static.go
type StaticConfig (line 24) | type StaticConfig struct
method ToMiddleware (line 156) | func (config StaticConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
constant directoryListHTMLTemplate (line 65) | directoryListHTMLTemplate = `
function Static (line 144) | func Static(root string) echo.MiddlewareFunc {
function StaticWithConfig (line 151) | func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
function serveFile (line 295) | func serveFile(c *echo.Context, file fs.File, info os.FileInfo) error {
function listDir (line 304) | func listDir(t *template.Template, pathInFs string, filesystem fs.FS, re...
function format (line 341) | func format(b int64) string {
FILE: middleware/static_other.go
function isIgnorableOpenFileError (line 13) | func isIgnorableOpenFileError(err error) bool {
FILE: middleware/static_test.go
function TestStatic_useCaseForApiAndSPAs (line 18) | func TestStatic_useCaseForApiAndSPAs(t *testing.T) {
function TestStatic (line 47) | func TestStatic(t *testing.T) {
function TestMustStaticWithConfig_panicsInvalidDirListTemplate (line 283) | func TestMustStaticWithConfig_panicsInvalidDirListTemplate(t *testing.T) {
function TestFormat (line 289) | func TestFormat(t *testing.T) {
function TestStatic_CustomFS (line 353) | func TestStatic_CustomFS(t *testing.T) {
function TestStatic_DirectoryBrowsing (line 446) | func TestStatic_DirectoryBrowsing(t *testing.T) {
FILE: middleware/util.go
constant _ (line 17) | _ = int64(1 << (10 * iota))
constant KB (line 19) | KB
constant MB (line 21) | MB
constant GB (line 23) | GB
constant TB (line 25) | TB
constant PB (line 27) | PB
constant EB (line 29) | EB
function matchScheme (line 32) | func matchScheme(domain, pattern string) bool {
function createRandomStringGenerator (line 38) | func createRandomStringGenerator(length uint8) func() string {
constant randomStringCharset (line 49) | randomStringCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw...
constant randomStringCharsetLen (line 50) | randomStringCharsetLen = 52
constant randomStringMaxByte (line 51) | randomStringMaxByte = 255 - (256 % randomStringCharsetLen)
function randomString (line 53) | func randomString(length uint8) string {
function validateOrigins (line 86) | func validateOrigins(origins []string, what string) error {
function validateOrigin (line 95) | func validateOrigin(origin string, what string) error {
FILE: middleware/util_test.go
type discardHandler (line 15) | type discardHandler struct
method Enabled (line 19) | func (d *discardHandler) Enabled(context.Context, slog.Level) bool { r...
function Test_matchScheme (line 21) | func Test_matchScheme(t *testing.T) {
function TestRandomString (line 53) | func TestRandomString(t *testing.T) {
function TestRandomStringBias (line 77) | func TestRandomStringBias(t *testing.T) {
function TestValidateOrigins (line 105) | func TestValidateOrigins(t *testing.T) {
FILE: renderer.go
type Renderer (line 9) | type Renderer interface
type TemplateRenderer (line 23) | type TemplateRenderer struct
method Render (line 30) | func (t *TemplateRenderer) Render(c *Context, w io.Writer, name string...
FILE: renderer_test.go
function TestRenderWithTemplateRenderer (line 15) | func TestRenderWithTemplateRenderer(t *testing.T) {
FILE: response.go
type Response (line 18) | type Response struct
method Before (line 36) | func (r *Response) Before(fn func()) {
method After (line 41) | func (r *Response) After(fn func()) {
method WriteHeader (line 49) | func (r *Response) WriteHeader(code int) {
method Write (line 63) | func (r *Response) Write(b []byte) (n int, err error) {
method Flush (line 81) | func (r *Response) Flush() {
method Hijack (line 92) | func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
method Unwrap (line 105) | func (r *Response) Unwrap() http.ResponseWriter {
method reset (line 109) | func (r *Response) reset(w http.ResponseWriter) {
function NewResponse (line 31) | func NewResponse(w http.ResponseWriter, logger *slog.Logger) (r *Respons...
function UnwrapResponse (line 120) | func UnwrapResponse(rw http.ResponseWriter) (*Response, error) {
type delayedStatusWriter (line 136) | type delayedStatusWriter struct
method WriteHeader (line 142) | func (w *delayedStatusWriter) WriteHeader(statusCode int) {
method Write (line 148) | func (w *delayedStatusWriter) Write(data []byte) (int, error) {
method Flush (line 159) | func (w *delayedStatusWriter) Flush() {
method Hijack (line 166) | func (w *delayedStatusWriter) Hijack() (net.Conn, *bufio.ReadWriter, e...
method Unwrap (line 170) | func (w *delayedStatusWriter) Unwrap() http.ResponseWriter {
FILE: response_test.go
function TestResponse (line 14) | func TestResponse(t *testing.T) {
function TestResponse_Write_FallsBackToDefaultStatus (line 34) | func TestResponse_Write_FallsBackToDefaultStatus(t *testing.T) {
function TestResponse_Write_UsesSetResponseCode (line 43) | func TestResponse_Write_UsesSetResponseCode(t *testing.T) {
function TestResponse_ChangeStatusCodeBeforeWrite (line 53) | func TestResponse_ChangeStatusCodeBeforeWrite(t *testing.T) {
function TestResponse_Unwrap (line 69) | func TestResponse_Unwrap(t *testing.T) {
function TestResponse_isHijacker (line 77) | func TestResponse_isHijacker(t *testing.T) {
function TestResponse_Flush (line 84) | func TestResponse_Flush(t *testing.T) {
type testResponseWriter (line 94) | type testResponseWriter struct
method WriteHeader (line 97) | func (w *testResponseWriter) WriteHeader(statusCode int) {
method Write (line 100) | func (w *testResponseWriter) Write([]byte) (int, error) {
method Header (line 104) | func (w *testResponseWriter) Header() http.Header {
function TestResponse_FlushPanics (line 108) | func TestResponse_FlushPanics(t *testing.T) {
function TestResponse_UnwrapResponse (line 119) | func TestResponse_UnwrapResponse(t *testing.T) {
function TestResponse_UnwrapResponse_error (line 127) | func TestResponse_UnwrapResponse_error(t *testing.T) {
FILE: route.go
type Route (line 16) | type Route struct
method ToRouteInfo (line 25) | func (r Route) ToRouteInfo(params []string) RouteInfo {
method WithPrefix (line 40) | func (r Route) WithPrefix(pathPrefix string, middlewares []MiddlewareF...
type RouteInfo (line 53) | type RouteInfo struct
method Clone (line 65) | func (r RouteInfo) Clone() RouteInfo {
method Reverse (line 75) | func (r RouteInfo) Reverse(pathValues ...any) string {
function HandlerName (line 99) | func HandlerName(h HandlerFunc) string {
method Clone (line 108) | func (r Routes) Clone() Routes {
method Reverse (line 117) | func (r Routes) Reverse(routeName string, pathValues ...any) (string, er...
method FindByMethodPath (line 127) | func (r Routes) FindByMethodPath(method string, path string) (RouteInfo,...
method FilterByMethod (line 141) | func (r Routes) FilterByMethod(method string) (Routes, error) {
method FilterByPath (line 159) | func (r Routes) FilterByPath(path string) (Routes, error) {
method FilterByName (line 177) | func (r Routes) FilterByName(name string) (Routes, error) {
FILE: route_test.go
type NameStruct (line 18) | type NameStruct struct
method getUsers (line 21) | func (n *NameStruct) getUsers(c *Context) error {
function TestHandlerName (line 25) | func TestHandlerName(t *testing.T) {
function TestHandlerName_differentFuncSameName (line 69) | func TestHandlerName_differentFuncSameName(t *testing.T) {
function TestRoute_ToRouteInfo (line 82) | func TestRoute_ToRouteInfo(t *testing.T) {
function TestRoute_ForGroup (line 136) | func TestRoute_ForGroup(t *testing.T) {
function exampleRoutes (line 161) | func exampleRoutes() Routes {
function TestRoutes_Clone (line 196) | func TestRoutes_Clone(t *testing.T) {
function TestRoutes_FindByMethodPath (line 208) | func TestRoutes_FindByMethodPath(t *testing.T) {
function TestRoutes_FilterByMethod (line 259) | func TestRoutes_FilterByMethod(t *testing.T) {
function TestRoutes_FilterByPath (line 311) | func TestRoutes_FilterByPath(t *testing.T) {
function TestRoutes_FilterByName (line 363) | func TestRoutes_FilterByName(t *testing.T) {
function TestRouteInfo_Reverse (line 421) | func TestRouteInfo_Reverse(t *testing.T) {
FILE: router.go
type Router (line 21) | type Router interface
constant NotFoundRouteName (line 49) | NotFoundRouteName = "echo_route_not_found_name"
constant MethodNotAllowedRouteName (line 51) | MethodNotAllowedRouteName = "echo_route_method_not_allowed_name"
type Routes (line 55) | type Routes
type DefaultRouter (line 60) | type DefaultRouter struct
method Routes (line 319) | func (r *DefaultRouter) Routes() Routes {
method Remove (line 324) | func (r *DefaultRouter) Remove(method string, path string) error {
method Add (line 447) | func (r *DefaultRouter) Add(route Route) (RouteInfo, error) {
method storeRouteInfo (line 538) | func (r *DefaultRouter) storeRouteInfo(ri RouteInfo) {
method insert (line 548) | func (r *DefaultRouter) insert(t kind, path string, method string, ri ...
method Route (line 791) | func (r *DefaultRouter) Route(c *Context) HandlerFunc {
type RouterConfig (line 75) | type RouterConfig struct
function NewRouter (line 85) | func NewRouter(config RouterConfig) *DefaultRouter {
type children (line 114) | type children
type node (line 116) | type node struct
method addStaticChild (line 705) | func (n *node) addStaticChild(c *node) {
method findStaticChild (line 709) | func (n *node) findStaticChild(l byte) *node {
method findChildWithLabel (line 718) | func (n *node) findChildWithLabel(l byte) *node {
method setHandler (line 731) | func (n *node) setHandler(method string, r *routeMethod) {
type kind (line 131) | type kind
constant staticKind (line 134) | staticKind kind = iota
constant paramKind (line 135) | paramKind
constant anyKind (line 136) | anyKind
constant paramLabel (line 138) | paramLabel = byte(':')
constant anyLabel (line 139) | anyLabel = byte('*')
type routeMethod (line 142) | type routeMethod struct
type routeMethods (line 148) | type routeMethods struct
method set (line 171) | func (m *routeMethods) set(method string, r *routeMethod) {
method find (line 213) | func (m *routeMethods) find(method string, fallbackToAny bool) *routeM...
method updateAllowHeader (line 251) | func (m *routeMethods) updateAllowHeader() {
method isHandler (line 301) | func (m *routeMethods) isHandler() bool {
type AddRouteError (line 428) | type AddRouteError struct
method Error (line 434) | func (e *AddRouteError) Error() string { return e.Method + " " + e.Pat...
method Unwrap (line 436) | func (e *AddRouteError) Unwrap() error { return e.Err }
function newAddRouteError (line 438) | func newAddRouteError(route Route, err error) *AddRouteError {
function normalizePathSlash (line 529) | func normalizePathSlash(path string) string {
function newNode (line 678) | func newNode(
type PathValues (line 1048) | type PathValues
method Get (line 1057) | func (p PathValues) Get(name string) (string, bool) {
method GetOr (line 1067) | func (p PathValues) GetOr(name string, defaultValue string) string {
type PathValue (line 1051) | type PathValue struct
FILE: router_concurrent.go
function NewConcurrentRouter (line 9) | func NewConcurrentRouter(r Router) Router {
type concurrentRouter (line 16) | type concurrentRouter struct
method Route (line 21) | func (r *concurrentRouter) Route(c *Context) HandlerFunc {
method Routes (line 28) | func (r *concurrentRouter) Routes() Routes {
method Add (line 35) | func (r *concurrentRouter) Add(routable Route) (RouteInfo, error) {
method Remove (line 42) | func (r *concurrentRouter) Remove(method string, path string) error {
FILE: router_concurrent_test.go
function TestConcurrentRouter_Remove (line 17) | func TestConcurrentRouter_Remove(t *testing.T) {
function TestConcurrentRouter_ConcurrentReads (line 33) | func TestConcurrentRouter_ConcurrentReads(t *testing.T) {
function TestConcurrentRouter_ConcurrentWrites (line 95) | func TestConcurrentRouter_ConcurrentWrites(t *testing.T) {
function TestConcurrentRouter_ConcurrentReadWrite (line 141) | func TestConcurrentRouter_ConcurrentReadWrite(t *testing.T) {
function TestConcurrentRouter_RoutesIterationDuringModification (line 225) | func TestConcurrentRouter_RoutesIterationDuringModification(t *testing.T) {
function TestConcurrentRouter_ParametersNoRace (line 299) | func TestConcurrentRouter_ParametersNoRace(t *testing.T) {
FILE: router_test.go
type testRoute (line 16) | type testRoute struct
function checkUnusedParamValues (line 659) | func checkUnusedParamValues(t *testing.T, c *Context, expectParam map[st...
function TestRouterFillsRequestPatternField (line 674) | func TestRouterFillsRequestPatternField(t *testing.T) {
function TestRouterStatic (line 689) | func TestRouterStatic(t *testing.T) {
function TestRouterParam (line 705) | func TestRouterParam(t *testing.T) {
function TestRouter_addAndMatchAllSupportedMethods (line 745) | func TestRouter_addAndMatchAllSupportedMethods(t *testing.T) {
function TestRouterAllowHeaderForAnyOtherMethodType (line 805) | func TestRouterAllowHeaderForAnyOtherMethodType(t *testing.T) {
function TestMethodNotAllowedAndNotFound (line 829) | func TestMethodNotAllowedAndNotFound(t *testing.T) {
function TestRouterOptionsMethodHandler (line 901) | func TestRouterOptionsMethodHandler(t *testing.T) {
function TestRouterHandleMethodOptions (line 925) | func TestRouterHandleMethodOptions(t *testing.T) {
function TestRouterTwoParam (line 993) | func TestRouterTwoParam(t *testing.T) {
function TestRouterParamWithSlash (line 1006) | func TestRouterParamWithSlash(t *testing.T) {
function TestRouteMultiLevelBacktracking (line 1056) | func TestRouteMultiLevelBacktracking(t *testing.T) {
function TestRouteMultiLevelBacktracking2 (line 1138) | func TestRouteMultiLevelBacktracking2(t *testing.T) {
function TestRouterBacktrackingFromMultipleParamKinds (line 1216) | func TestRouterBacktrackingFromMultipleParamKinds(t *testing.T) {
function TestRouterParamStaticConflict (line 1282) | func TestRouterParamStaticConflict(t *testing.T) {
function TestRouterParam_escapeColon (line 1322) | func TestRouterParam_escapeColon(t *testing.T) {
function TestRouterMatchAny (line 1387) | func TestRouterMatchAny(t *testing.T) {
function TestRouterAnyMatchesLastAddedAnyRoute (line 1437) | func TestRouterAnyMatchesLastAddedAnyRoute(t *testing.T) {
function TestRouterMatchAnyPrefixIssue (line 1463) | func TestRouterMatchAnyPrefixIssue(t *testing.T) {
function TestRouterMatchAnySlash (line 1519) | func TestRouterMatchAnySlash(t *testing.T) {
function TestRouterMatchAnyMultiLevel (line 1603) | func TestRouterMatchAnyMultiLevel(t *testing.T) {
function TestRouterMatchAnyMultiLevelWithPost (line 1676) | func TestRouterMatchAnyMultiLevelWithPost(t *testing.T) {
function TestRouterMicroParam (line 1743) | func TestRouterMicroParam(t *testing.T) {
function TestRouterMixParamMatchAny (line 1757) | func TestRouterMixParamMatchAny(t *testing.T) {
function TestRouterMultiRoute (line 1773) | func TestRouterMultiRoute(t *testing.T) {
function TestRouterPriority (line 1825) | func TestRouterPriority(t *testing.T) {
function TestRouterIssue1348 (line 1939) | func TestRouterIssue1348(t *testing.T) {
function TestRouterPriorityNotFound (line 1947) | func TestRouterPriorityNotFound(t *testing.T) {
function TestRouterParamNames (line 2002) | func TestRouterParamNames(t *testing.T) {
function TestRouterStaticDynamicConflict (line 2063) | func TestRouterStaticDynamicConflict(t *testing.T) {
function TestRouterParamBacktraceNotFound (line 2140) | func TestRouterParamBacktraceNotFound(t *testing.T) {
function testRouterAPI (line 2217) | func testRouterAPI(t *testing.T, api []testRoute) {
function TestRouterGitHubAPI (line 2248) | func TestRouterGitHubAPI(t *testing.T) {
function TestRouter_Match_DifferentParamNamesForSamePlace (line 2252) | func TestRouter_Match_DifferentParamNamesForSamePlace(t *testing.T) {
function TestDefaultRouter_PathValuesCanMatchEmptyValues (line 2314) | func TestDefaultRouter_PathValuesCanMatchEmptyValues(t *testing.T) {
function TestRouterParamAlias (line 2371) | func TestRouterParamAlias(t *testing.T) {
function TestRouterParamOrdering (line 2381) | func TestRouterParamOrdering(t *testing.T) {
function TestRouterMixedParams (line 2403) | func TestRouterMixedParams(t *testing.T) {
function TestRouterParam1466 (line 2417) | func TestRouterParam1466(t *testing.T) {
function TestPathValuesSizeOverMultipleRequests (line 2519) | func TestPathValuesSizeOverMultipleRequests(t *testing.T) {
function TestRouterFindNotPanicOrLoopsWhenContextSetParamValuesIsCalledWithLessValuesThanEchoMaxParam (line 2549) | func TestRouterFindNotPanicOrLoopsWhenContextSetParamValuesIsCalledWithL...
function TestRouterPanicWhenParamNoRootOnlyChildsFailsFind (line 2593) | func TestRouterPanicWhenParamNoRootOnlyChildsFailsFind(t *testing.T) {
function TestRouter_addEmptyPathToSlashReverse (line 2649) | func TestRouter_addEmptyPathToSlashReverse(t *testing.T) {
function TestRouter_ReverseNotFound (line 2660) | func TestRouter_ReverseNotFound(t *testing.T) {
function TestRoutes_ReverseHandlerName (line 2671) | func TestRoutes_ReverseHandlerName(t *testing.T) {
function TestRoutes_Reverse (line 2747) | func TestRoutes_Reverse(t *testing.T) {
function TestRouter_Routes (line 2835) | func TestRouter_Routes(t *testing.T) {
function TestRouterNoRoutablePath (line 2871) | func TestRouterNoRoutablePath(t *testing.T) {
function benchmarkRouterRoutes (line 2884) | func benchmarkRouterRoutes(b *testing.B, routes []testRoute, routesToFin...
function TestDefaultRouter_Remove (line 2920) | func TestDefaultRouter_Remove(t *testing.T) {
function TestDefaultRouter_AddWithoutHandler (line 3048) | func TestDefaultRouter_AddWithoutHandler(t *testing.T) {
function TestDefaultRouter_AddDuplicateRouteNotAllowed (line 3056) | func TestDefaultRouter_AddDuplicateRouteNotAllowed(t *testing.T) {
function TestDefaultRouter_UnescapePathParamValues (line 3083) | func TestDefaultRouter_UnescapePathParamValues(t *testing.T) {
function TestDefaultRouter_AddDuplicateRouteAllowed (line 3174) | func TestDefaultRouter_AddDuplicateRouteAllowed(t *testing.T) {
function TestDefaultRouter_UseEscapedPathForRouting (line 3211) | func TestDefaultRouter_UseEscapedPathForRouting(t *testing.T) {
function TestDefaultRouter_NotFoundHandler (line 3280) | func TestDefaultRouter_NotFoundHandler(t *testing.T) {
function TestDefaultRouter_MethodNotAllowedHandler (line 3297) | func TestDefaultRouter_MethodNotAllowedHandler(t *testing.T) {
function TestDefaultRouter_OptionsMethodHandler (line 3314) | func TestDefaultRouter_OptionsMethodHandler(t *testing.T) {
function TestRouter_RouteWhenNotFoundRouteWithNodeSplitting (line 3331) | func TestRouter_RouteWhenNotFoundRouteWithNodeSplitting(t *testing.T) {
function TestRouter_RouteWhenNotFoundRouteAnyKind (line 3366) | func TestRouter_RouteWhenNotFoundRouteAnyKind(t *testing.T) {
function TestRouter_RouteWhenNotFoundRouteParamKind (line 3435) | func TestRouter_RouteWhenNotFoundRouteParamKind(t *testing.T) {
function TestRouter_RouteWhenNotFoundRouteStaticKind (line 3504) | func TestRouter_RouteWhenNotFoundRouteStaticKind(t *testing.T) {
function TestPathValues_Get (line 3557) | func TestPathValues_Get(t *testing.T) {
function TestPathValues_GetOr (line 3591) | func TestPathValues_GetOr(t *testing.T) {
function BenchmarkRouterStaticRoutes (line 3624) | func BenchmarkRouterStaticRoutes(b *testing.B) {
function BenchmarkRouterStaticRoutesMisses (line 3628) | func BenchmarkRouterStaticRoutesMisses(b *testing.B) {
function BenchmarkRouterGitHubAPI (line 3632) | func BenchmarkRouterGitHubAPI(b *testing.B) {
function BenchmarkRouterGitHubAPIMisses (line 3636) | func BenchmarkRouterGitHubAPIMisses(b *testing.B) {
function BenchmarkRouterParseAPI (line 3640) | func BenchmarkRouterParseAPI(b *testing.B) {
function BenchmarkRouterParseAPIMisses (line 3644) | func BenchmarkRouterParseAPIMisses(b *testing.B) {
function BenchmarkRouterGooglePlusAPI (line 3648) | func BenchmarkRouterGooglePlusAPI(b *testing.B) {
function BenchmarkRouterGooglePlusAPIMisses (line 3652) | func BenchmarkRouterGooglePlusAPIMisses(b *testing.B) {
function BenchmarkRouterParamsAndAnyAPI (line 3656) | func BenchmarkRouterParamsAndAnyAPI(b *testing.B) {
FILE: server.go
constant banner (line 21) | banner = "Echo (v%s). High performance, minimalist Go web framework http...
type StartConfig (line 25) | type StartConfig struct
method Start (line 63) | func (sc StartConfig) Start(ctx stdContext.Context, h http.Handler) er...
method StartTLS (line 70) | func (sc StartConfig) StartTLS(ctx stdContext.Context, h http.Handler,...
method start (line 99) | func (sc StartConfig) start(ctx stdContext.Context, h http.Handler) er...
function filepathOrContent (line 171) | func filepathOrContent(fileOrContent any, certFilesystem fs.FS) (content...
function gracefulShutdown (line 182) | func gracefulShutdown(shutdownCtx stdContext.Context, sc *StartConfig, s...
FILE: server_test.go
function startOnRandomPort (line 28) | func startOnRandomPort(ctx stdContext.Context, e *Echo) (string, error) {
function waitForServerStart (line 45) | func waitForServerStart(addrChan <-chan string, errCh <-chan error) (str...
function doGet (line 66) | func doGet(url string) (int, string, error) {
function TestStartConfig_Start (line 80) | func TestStartConfig_Start(t *testing.T) {
function TestStartConfig_GracefulShutdown (line 132) | func TestStartConfig_GracefulShutdown(t *testing.T) {
function TestStartConfig_Start_createListenerError (line 228) | func TestStartConfig_Start_createListenerError(t *testing.T) {
function TestStartConfig_StartTLS (line 242) | func TestStartConfig_StartTLS(t *testing.T) {
function TestFilepathOrContent (line 323) | func TestFilepathOrContent(t *testing.T) {
function supportsIPv6 (line 399) | func supportsIPv6() bool {
function TestStartConfig_WithListenerNetwork (line 410) | func TestStartConfig_WithListenerNetwork(t *testing.T) {
function TestStartConfig_WithHideBanner (line 479) | func TestStartConfig_WithHideBanner(t *testing.T) {
function TestStartConfig_WithHidePort (line 540) | func TestStartConfig_WithHidePort(t *testing.T) {
function TestStartConfig_WithBeforeServeFunc (line 599) | func TestStartConfig_WithBeforeServeFunc(t *testing.T) {
function TestStartConfig_WithHTTP2WithCustomTlsConfig (line 616) | func TestStartConfig_WithHTTP2WithCustomTlsConfig(t *testing.T) {
FILE: version.go
constant Version (line 8) | Version = "5.0.4"
FILE: vhost.go
function NewVirtualHostHandler (line 10) | func NewVirtualHostHandler(vhosts map[string]*Echo) *Echo {
FILE: vhost_test.go
function TestVirtualHostHandler (line 13) | func TestVirtualHostHandler(t *testing.T) {
Condensed preview — 127 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,319K chars).
[
{
"path": ".editorconfig",
"chars": 467,
"preview": "# EditorConfig coding styles definitions. For more information about the\n# properties used in this file, please see the "
},
{
"path": ".gitattributes",
"chars": 497,
"preview": "# Automatically normalize line endings for all text-based files\n# http://git-scm.com/docs/gitattributes#_end_of_line_con"
},
{
"path": ".github/FUNDING.yml",
"chars": 644,
"preview": "# These are supported funding model platforms\n\ngithub: [labstack]\npatreon: # Replace with a single Patreon username\nopen"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 547,
"preview": "### Issue Description\n\n### Working code to debug\n\n```go\npackage main\n\nimport (\n \"github.com/labstack/echo/v5\"\n \"net/ht"
},
{
"path": ".github/stale.yml",
"chars": 722,
"preview": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a "
},
{
"path": ".github/workflows/checks.yml",
"chars": 997,
"preview": "name: Run checks\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n workflow_dispa"
},
{
"path": ".github/workflows/echo.yml",
"chars": 2453,
"preview": "name: Run Tests\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n workflow_dispat"
},
{
"path": ".gitignore",
"chars": 62,
"preview": ".DS_Store\ncoverage.txt\n_test\nvendor\n.idea\n*.iml\n*.out\n.vscode\n"
},
{
"path": "API_CHANGES_V5.md",
"chars": 29979,
"preview": "# Echo v5 Public API Changes\n\n**Comparison between `master` (v4.15.0) and `v5` (v5.0.0-alpha) branches**\n\nGenerated: 202"
},
{
"path": "CHANGELOG.md",
"chars": 42224,
"preview": "# Changelog\n\n## v5.0.4 - 2026-02-15\n\n**Enhancements**\n\n* Remove unused import 'errors' from README example by @kumapower"
},
{
"path": "CLAUDE.md",
"chars": 3135,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2022 LabStack\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "Makefile",
"chars": 1021,
"preview": "PKG := \"github.com/labstack/echo\"\nPKG_LIST := $(shell go list ${PKG}/...)\n\n.DEFAULT_GOAL := check\ncheck: lint vet race #"
},
{
"path": "README.md",
"chars": 9652,
"preview": "[](https://sourcegraph.com"
},
{
"path": "SECURITY.md",
"chars": 451,
"preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n|-----------|-------------"
},
{
"path": "_fixture/_fixture/README.md",
"chars": 53,
"preview": "This directory is used for the static middleware test"
},
{
"path": "_fixture/certs/README.md",
"chars": 384,
"preview": "To generate a valid certificate and private key use the following command:\n\n```bash\n# In OpenSSL ≥ 1.1.1\nopenssl req -x5"
},
{
"path": "_fixture/certs/cert.pem",
"chars": 1870,
"preview": "-----BEGIN CERTIFICATE-----\nMIIFODCCAyCgAwIBAgIUaTvDluaMf+VJgYHQ0HFTS3yuCHYwDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWx"
},
{
"path": "_fixture/certs/key.pem",
"chars": 3272,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCerLIACdYUfZMf\nsSmPnJ9nFisG+LPJ1l7Vaj/VakZ"
},
{
"path": "_fixture/dist/private.txt",
"chars": 13,
"preview": "private file\n"
},
{
"path": "_fixture/dist/public/assets/readme.md",
"chars": 17,
"preview": "readme in assets\n"
},
{
"path": "_fixture/dist/public/assets/subfolder/subfolder.md",
"chars": 22,
"preview": "file inside subfolder\n"
},
{
"path": "_fixture/dist/public/index.html",
"chars": 26,
"preview": "<h1>Hello from index</h1>\n"
},
{
"path": "_fixture/dist/public/test.txt",
"chars": 18,
"preview": "test.txt contents\n"
},
{
"path": "_fixture/folder/index.html",
"chars": 122,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>Echo</title>\n</head>\n<body>\n</body>\n</html"
},
{
"path": "_fixture/index.html",
"chars": 122,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>Echo</title>\n</head>\n<body>\n</body>\n</html"
},
{
"path": "bind.go",
"chars": 14899,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "bind_test.go",
"chars": 53016,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "binder.go",
"chars": 43845,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "binder_external_test.go",
"chars": 3888,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\n// run tests as ex"
},
{
"path": "binder_generic.go",
"chars": 16971,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "binder_generic_test.go",
"chars": 37214,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "binder_test.go",
"chars": 95169,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "codecov.yml",
"chars": 151,
"preview": "coverage:\n status:\n project:\n default:\n threshold: 1%\n patch:\n default:\n threshold: 1%\n\nc"
},
{
"path": "context.go",
"chars": 20329,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "context_generic.go",
"chars": 1260,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "context_generic_test.go",
"chars": 1454,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "context_test.go",
"chars": 36815,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "echo.go",
"chars": 30412,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\n/*\nPackage echo im"
},
{
"path": "echo_test.go",
"chars": 32473,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "echotest/context.go",
"chars": 5482,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echotest\n\n"
},
{
"path": "echotest/context_external_test.go",
"chars": 621,
"preview": "package echotest_test\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/labstack/echo/v5/ech"
},
{
"path": "echotest/context_test.go",
"chars": 3745,
"preview": "package echotest\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v5\"\n\t\"github.com/str"
},
{
"path": "echotest/reader.go",
"chars": 1008,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echotest\n\n"
},
{
"path": "echotest/reader_external_test.go",
"chars": 579,
"preview": "package echotest_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v5/echotest\"\n\t\"github.com/stretchr/tes"
},
{
"path": "echotest/reader_test.go",
"chars": 429,
"preview": "package echotest\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst testJSONContent = `{\n \"field\": \"v"
},
{
"path": "echotest/testdata/test.json",
"chars": 23,
"preview": "{\n \"field\": \"value\"\n}\n"
},
{
"path": "go.mod",
"chars": 335,
"preview": "module github.com/labstack/echo/v5\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/stretchr/testify v1.11.1\n\tgolang.org/x/net v0.49.0\n"
},
{
"path": "go.sum",
"chars": 1346,
"preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.m"
},
{
"path": "group.go",
"chars": 6710,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "group_test.go",
"chars": 23555,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "httperror.go",
"chars": 5258,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "httperror_external_test.go",
"chars": 1060,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\n// run tests as ex"
},
{
"path": "httperror_test.go",
"chars": 4614,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "ip.go",
"chars": 9371,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "ip_test.go",
"chars": 21510,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "json.go",
"chars": 891,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "json_test.go",
"chars": 2741,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "middleware/DEVELOPMENT.md",
"chars": 692,
"preview": "# Development Guidelines for middlewares\n\n## Best practices:\n\n* Do not use `panic` in middleware creator functions in ca"
},
{
"path": "middleware/basic_auth.go",
"chars": 4931,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/basic_auth_test.go",
"chars": 7026,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/body_dump.go",
"chars": 5677,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/body_dump_test.go",
"chars": 16746,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/body_limit.go",
"chars": 2751,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/body_limit_test.go",
"chars": 4378,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/compress.go",
"chars": 6271,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/compress_test.go",
"chars": 10851,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/context_timeout.go",
"chars": 1999,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/context_timeout_test.go",
"chars": 6300,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/cors.go",
"chars": 12042,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/cors_test.go",
"chars": 20361,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/csrf.go",
"chars": 10483,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/csrf_test.go",
"chars": 25418,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/decompress.go",
"chars": 4678,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/decompress_test.go",
"chars": 13851,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/extractor.go",
"chars": 8473,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/extractor_test.go",
"chars": 17006,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/key_auth.go",
"chars": 7503,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/key_auth_test.go",
"chars": 10693,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/method_override.go",
"chars": 2890,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/method_override_test.go",
"chars": 2316,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/middleware.go",
"chars": 2398,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/middleware_test.go",
"chars": 3739,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/proxy.go",
"chars": 14581,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/proxy_test.go",
"chars": 27689,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/rate_limiter.go",
"chars": 8712,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/rate_limiter_test.go",
"chars": 17890,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/recover.go",
"chars": 2788,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/recover_test.go",
"chars": 3605,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/redirect.go",
"chars": 6131,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/redirect_test.go",
"chars": 7864,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/request_id.go",
"chars": 2371,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/request_id_test.go",
"chars": 4570,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/request_logger.go",
"chars": 16142,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/request_logger_test.go",
"chars": 17570,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/rewrite.go",
"chars": 2339,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/rewrite_test.go",
"chars": 9237,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/secure.go",
"chars": 5619,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/secure_test.go",
"chars": 4741,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/slash.go",
"chars": 4782,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/slash_test.go",
"chars": 6898,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/static.go",
"chars": 9411,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/static_other.go",
"chars": 1015,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/static_test.go",
"chars": 14842,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/testdata/dist/private.txt",
"chars": 13,
"preview": "private file\n"
},
{
"path": "middleware/testdata/dist/public/assets/readme.md",
"chars": 54,
"preview": "This directory is used for the static middleware test\n"
},
{
"path": "middleware/testdata/dist/public/assets/subfolder/subfolder.md",
"chars": 22,
"preview": "file inside subfolder\n"
},
{
"path": "middleware/testdata/dist/public/index.html",
"chars": 26,
"preview": "<h1>Hello from index</h1>\n"
},
{
"path": "middleware/testdata/dist/public/test.txt",
"chars": 18,
"preview": "test.txt contents\n"
},
{
"path": "middleware/testdata/private.txt",
"chars": 13,
"preview": "private file\n"
},
{
"path": "middleware/util.go",
"chars": 2931,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "middleware/util_test.go",
"chars": 8715,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage middleware"
},
{
"path": "renderer.go",
"chars": 971,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "renderer_test.go",
"chars": 737,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "response.go",
"chars": 5612,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "response_test.go",
"chars": 3101,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "route.go",
"chars": 5062,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "route_test.go",
"chars": 12150,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "router.go",
"chars": 31688,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "router_concurrent.go",
"chars": 886,
"preview": "package echo\n\nimport (\n\t\"sync\"\n)\n\n// NewConcurrentRouter creates concurrency safe Router which routes can be added/remov"
},
{
"path": "router_concurrent_test.go",
"chars": 10050,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "router_test.go",
"chars": 105028,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "server.go",
"chars": 6187,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "server_test.go",
"chars": 16423,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "version.go",
"chars": 165,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\ncons"
},
{
"path": "vhost.go",
"chars": 603,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
},
{
"path": "vhost_test.go",
"chars": 3124,
"preview": "// SPDX-License-Identifier: MIT\n// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors\n\npackage echo\n\nimpo"
}
]
About this extraction
This page contains the full source code of the labstack/echo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 127 files (1.1 MB), approximately 336.6k tokens, and a symbol index with 1412 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.