Skip to content

Commit 6ef5f77

Browse files
committed
WIP: logger examples
WIP: make default logger implemented custom writer for jsonlike logs WIP: improve examples WIP: defaultErrorHandler use errors.As to unwrap errors. Update readme WIP: default logger logs json, restore e.Start method WIP: clean router.Match a bit WIP: func types/fields have echo.Context has first element WIP: remove yaml tags as functions etc can not be serialized anyway WIP: change BindPathParams,BindQueryParams,BindHeaders from methods to functions and reverse arguments to be like DefaultBinder.Bind is WIP: improved comments, logger now extracts status from error WIP: go mod tidy WIP: rebase with 4.5.0 WIP: * removed todos. * removed StartAutoTLS and StartH2CServer methods from `StartConfig` * KeyAuth middleware errorhandler can swallow the error and resume next middleware WIP: add RouterConfig.UseEscapedPathForMatching to use escaped path for matching request against routes WIP: FIXMEs WIP: upgrade golang-jwt/jwt to `v4` WIP: refactor http methods to return RouteInfo WIP: refactor static not creating multiple routes WIP: refactor route and middleware adding functions not to return error directly WIP: Use 401 for problematic/missing headers for key auth and JWT middleware (#1552, #1402). > In summary, a 401 Unauthorized response should be used for missing or bad authentication WIP: replace `HTTPError.SetInternal` with `HTTPError.WithInternal` so we could not mutate global error variables WIP: add RouteInfo and RouteMatchType into Context what we could know from in middleware what route was matched and/or type of that match (200/404/405) WIP: make notFoundHandler and methodNotAllowedHandler private. encourage that all errors be handled in Echo.HTTPErrorHandler WIP: server cleanup ideas WIP: routable.ForGroup WIP: note about logger middleware WIP: bind should not default values on second try. use crypto rand for better randomness WIP: router add route as interface and returns info as interface WIP: improve flaky test (remains still flaky) WIP: add notes about bind default values WIP: every route can have their own path params names WIP: routerCreator and different tests WIP: different things WIP: remove route implementation WIP: support custom method types WIP: extractor tests WIP: v5.0.x proposal over v4.4.0
1 parent c6f0c66 commit 6ef5f77

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+9177
-4883
lines changed

.github/workflows/echo.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ jobs:
2727
os: [ubuntu-latest, macos-latest, windows-latest]
2828
# Each major Go release is supported until there are two newer major releases. https://golang.org/doc/devel/release.html#policy
2929
# Echo tests with last four major releases
30-
go: [1.14, 1.15, 1.16, 1.17]
30+
# except v5 starts from 1.16 until there is last four major releases after that
31+
go: [1.16, 1.17]
3132
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
3233
runs-on: ${{ matrix.os }}
3334
steps:

.travis.yml

-21
This file was deleted.

Makefile

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ race: ## Run tests with data race detector
2424
@go test -race ${PKG_LIST}
2525

2626
benchmark: ## Run benchmarks
27-
@go test -run="-" -bench=".*" ${PKG_LIST}
27+
@go test -run="-" -benchmem -bench=".*" ${PKG_LIST}
2828

2929
help: ## Display this help screen
3030
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
3131

32-
goversion ?= "1.15"
33-
test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15
32+
goversion ?= "1.16"
33+
test_version: ## Run tests inside Docker with given version (defaults to 1.16 oldest supported). Example: make test_version goversion=1.16
3434
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"

README.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
## Supported Go versions
1414

15+
Echo supports last four major releases. `v5` starts from 1.16 until there is last four major releases after that.
16+
1517
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
1618
Therefore a Go version capable of understanding /vN suffixed imports is required:
1719

@@ -67,8 +69,8 @@ package main
6769

6870
import (
6971
"net/http"
70-
"github.com/labstack/echo/v4"
71-
"github.com/labstack/echo/v4/middleware"
72+
"github.com/labstack/echo/v5"
73+
"github.com/labstack/echo/v5/middleware"
7274
)
7375

7476
func main() {
@@ -83,7 +85,9 @@ func main() {
8385
e.GET("/", hello)
8486

8587
// Start server
86-
e.Logger.Fatal(e.Start(":1323"))
88+
if err := e.Start(":1323"); err != http.ErrServerClosed {
89+
log.Fatal(err)
90+
}
8791
}
8892

8993
// Handler

bind.go

+42-46
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,38 @@ import (
1111
"strings"
1212
)
1313

14-
type (
15-
// Binder is the interface that wraps the Bind method.
16-
Binder interface {
17-
Bind(i interface{}, c Context) error
18-
}
14+
// Binder is the interface that wraps the Bind method.
15+
type Binder interface {
16+
Bind(c Context, i interface{}) error
17+
}
1918

20-
// DefaultBinder is the default implementation of the Binder interface.
21-
DefaultBinder struct{}
19+
// DefaultBinder is the default implementation of the Binder interface.
20+
type DefaultBinder struct{}
2221

23-
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
24-
// Types that don't implement this, but do implement encoding.TextUnmarshaler
25-
// will use that interface instead.
26-
BindUnmarshaler interface {
27-
// UnmarshalParam decodes and assigns a value from an form or query param.
28-
UnmarshalParam(param string) error
29-
}
30-
)
22+
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
23+
// Types that don't implement this, but do implement encoding.TextUnmarshaler
24+
// will use that interface instead.
25+
type BindUnmarshaler interface {
26+
// UnmarshalParam decodes and assigns a value from an form or query param.
27+
UnmarshalParam(param string) error
28+
}
3129

3230
// BindPathParams binds path params to bindable object
33-
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
34-
names := c.ParamNames()
35-
values := c.ParamValues()
31+
func BindPathParams(c Context, i interface{}) error {
3632
params := map[string][]string{}
37-
for i, name := range names {
38-
params[name] = []string{values[i]}
33+
for _, param := range c.PathParams() {
34+
params[param.Name] = []string{param.Value}
3935
}
40-
if err := b.bindData(i, params, "param"); err != nil {
41-
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
36+
if err := bindData(i, params, "param"); err != nil {
37+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
4238
}
4339
return nil
4440
}
4541

4642
// BindQueryParams binds query params to bindable object
47-
func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {
48-
if err := b.bindData(i, c.QueryParams(), "query"); err != nil {
49-
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
43+
func BindQueryParams(c Context, i interface{}) error {
44+
if err := bindData(i, c.QueryParams(), "query"); err != nil {
45+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
5046
}
5147
return nil
5248
}
@@ -56,7 +52,7 @@ func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {
5652
// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm
5753
// See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm
5854
// See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm
59-
func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
55+
func BindBody(c Context, i interface{}) (err error) {
6056
req := c.Request()
6157
if req.ContentLength == 0 {
6258
return
@@ -70,25 +66,25 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
7066
case *HTTPError:
7167
return err
7268
default:
73-
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
69+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
7470
}
7571
}
7672
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
7773
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
7874
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
79-
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
75+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
8076
} else if se, ok := err.(*xml.SyntaxError); ok {
81-
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
77+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error()))
8278
}
83-
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
79+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
8480
}
8581
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
8682
params, err := c.FormParams()
8783
if err != nil {
88-
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
84+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
8985
}
90-
if err = b.bindData(i, params, "form"); err != nil {
91-
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
86+
if err = bindData(i, params, "form"); err != nil {
87+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
9288
}
9389
default:
9490
return ErrUnsupportedMediaType
@@ -98,33 +94,33 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
9894

9995
// BindHeaders binds HTTP headers to a bindable object
10096
func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error {
101-
if err := b.bindData(i, c.Request().Header, "header"); err != nil {
102-
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
97+
if err := bindData(i, c.Request().Header, "header"); err != nil {
98+
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
10399
}
104100
return nil
105101
}
106102

107103
// Bind implements the `Binder#Bind` function.
108104
// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous
109-
// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
110-
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
111-
if err := b.BindPathParams(c, i); err != nil {
105+
// step bound values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
106+
func (b *DefaultBinder) Bind(c Context, i interface{}) (err error) {
107+
if err := BindPathParams(c, i); err != nil {
112108
return err
113109
}
114110
// Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH)
115111
// Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that.
116112
// i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding.
117113
// This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body
118114
if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
119-
if err = b.BindQueryParams(c, i); err != nil {
115+
if err = BindQueryParams(c, i); err != nil {
120116
return err
121117
}
122118
}
123-
return b.BindBody(c, i)
119+
return BindBody(c, i)
124120
}
125121

126122
// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
127-
func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error {
123+
func bindData(destination interface{}, data map[string][]string, tag string) error {
128124
if destination == nil || len(data) == 0 {
129125
return nil
130126
}
@@ -170,7 +166,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
170166
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
171167
// structs that implement BindUnmarshaler are binded only when they have explicit tag
172168
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
173-
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
169+
if err := bindData(structField.Addr().Interface(), data, tag); err != nil {
174170
return err
175171
}
176172
}
@@ -297,7 +293,7 @@ func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
297293

298294
func setIntField(value string, bitSize int, field reflect.Value) error {
299295
if value == "" {
300-
value = "0"
296+
return nil
301297
}
302298
intVal, err := strconv.ParseInt(value, 10, bitSize)
303299
if err == nil {
@@ -308,7 +304,7 @@ func setIntField(value string, bitSize int, field reflect.Value) error {
308304

309305
func setUintField(value string, bitSize int, field reflect.Value) error {
310306
if value == "" {
311-
value = "0"
307+
return nil
312308
}
313309
uintVal, err := strconv.ParseUint(value, 10, bitSize)
314310
if err == nil {
@@ -319,7 +315,7 @@ func setUintField(value string, bitSize int, field reflect.Value) error {
319315

320316
func setBoolField(value string, field reflect.Value) error {
321317
if value == "" {
322-
value = "false"
318+
return nil
323319
}
324320
boolVal, err := strconv.ParseBool(value)
325321
if err == nil {
@@ -330,7 +326,7 @@ func setBoolField(value string, field reflect.Value) error {
330326

331327
func setFloatField(value string, bitSize int, field reflect.Value) error {
332328
if value == "" {
333-
value = "0.0"
329+
return nil
334330
}
335331
floatVal, err := strconv.ParseFloat(value, bitSize)
336332
if err == nil {

0 commit comments

Comments
 (0)