Skip to content

Commit

Permalink
WIP: logger examples
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aldas committed Oct 2, 2021
1 parent c6f0c66 commit 6ef5f77
Show file tree
Hide file tree
Showing 80 changed files with 9,177 additions and 4,883 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/echo.yml
Expand Up @@ -27,7 +27,8 @@ jobs:
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
go: [1.14, 1.15, 1.16, 1.17]
# except v5 starts from 1.16 until there is last four major releases after that
go: [1.16, 1.17]
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
runs-on: ${{ matrix.os }}
steps:
Expand Down
21 changes: 0 additions & 21 deletions .travis.yml

This file was deleted.

6 changes: 3 additions & 3 deletions Makefile
Expand Up @@ -24,11 +24,11 @@ race: ## Run tests with data race detector
@go test -race ${PKG_LIST}

benchmark: ## Run benchmarks
@go test -run="-" -bench=".*" ${PKG_LIST}
@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.15"
test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15
goversion ?= "1.16"
test_version: ## Run tests inside Docker with given version (defaults to 1.16 oldest supported). Example: make test_version goversion=1.16
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"
10 changes: 7 additions & 3 deletions README.md
Expand Up @@ -12,6 +12,8 @@

## Supported Go versions

Echo supports last four major releases. `v5` starts from 1.16 until there is last four major releases after that.

As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
Therefore a Go version capable of understanding /vN suffixed imports is required:

Expand Down Expand Up @@ -67,8 +69,8 @@ package main

import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
)

func main() {
Expand All @@ -83,7 +85,9 @@ func main() {
e.GET("/", hello)

// Start server
e.Logger.Fatal(e.Start(":1323"))
if err := e.Start(":1323"); err != http.ErrServerClosed {
log.Fatal(err)
}
}

// Handler
Expand Down
88 changes: 42 additions & 46 deletions bind.go
Expand Up @@ -11,42 +11,38 @@ import (
"strings"
)

type (
// Binder is the interface that wraps the Bind method.
Binder interface {
Bind(i interface{}, c Context) error
}
// Binder is the interface that wraps the Bind method.
type Binder interface {
Bind(c Context, i interface{}) error
}

// DefaultBinder is the default implementation of the Binder interface.
DefaultBinder struct{}
// 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.
BindUnmarshaler interface {
// UnmarshalParam decodes and assigns a value from an form or query param.
UnmarshalParam(param string) error
}
)
// 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
}

// BindPathParams binds path params to bindable object
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
names := c.ParamNames()
values := c.ParamValues()
func BindPathParams(c Context, i interface{}) error {
params := map[string][]string{}
for i, name := range names {
params[name] = []string{values[i]}
for _, param := range c.PathParams() {
params[param.Name] = []string{param.Value}
}
if err := b.bindData(i, params, "param"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
if err := bindData(i, params, "param"); err != nil {
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
}
return nil
}

// BindQueryParams binds query params to bindable object
func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {
if err := b.bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
func BindQueryParams(c Context, i interface{}) error {
if err := bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
}
return nil
}
Expand All @@ -56,7 +52,7 @@ func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {
// 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 (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
func BindBody(c Context, i interface{}) (err error) {
req := c.Request()
if req.ContentLength == 0 {
return
Expand All @@ -70,25 +66,25 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
case *HTTPError:
return err
default:
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
}
}
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
} else if se, ok := err.(*xml.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error()))
}
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
}
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
params, err := c.FormParams()
if err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
}
if err = b.bindData(i, params, "form"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
if err = bindData(i, params, "form"); err != nil {
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
}
default:
return ErrUnsupportedMediaType
Expand All @@ -98,33 +94,33 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {

// BindHeaders binds HTTP headers to a bindable object
func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error {
if err := b.bindData(i, c.Request().Header, "header"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
if err := bindData(i, c.Request().Header, "header"); err != nil {
return NewHTTPErrorWithInternal(http.StatusBadRequest, err, err.Error())
}
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 binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.BindPathParams(c, i); err != nil {
// step bound values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
func (b *DefaultBinder) Bind(c Context, i interface{}) (err error) {
if err := BindPathParams(c, i); err != nil {
return err
}
// Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH)
// Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that.
// 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.
// This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body
if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
if err = b.BindQueryParams(c, i); err != nil {
if err = BindQueryParams(c, i); err != nil {
return err
}
}
return b.BindBody(c, i)
return BindBody(c, i)
}

// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error {
func bindData(destination interface{}, data map[string][]string, tag string) error {
if destination == nil || len(data) == 0 {
return nil
}
Expand Down Expand Up @@ -170,7 +166,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
// 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).
// structs that implement BindUnmarshaler are binded only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
if err := bindData(structField.Addr().Interface(), data, tag); err != nil {
return err
}
}
Expand Down Expand Up @@ -297,7 +293,7 @@ func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {

func setIntField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
return nil
}
intVal, err := strconv.ParseInt(value, 10, bitSize)
if err == nil {
Expand All @@ -308,7 +304,7 @@ func setIntField(value string, bitSize int, field reflect.Value) error {

func setUintField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
return nil
}
uintVal, err := strconv.ParseUint(value, 10, bitSize)
if err == nil {
Expand All @@ -319,7 +315,7 @@ func setUintField(value string, bitSize int, field reflect.Value) error {

func setBoolField(value string, field reflect.Value) error {
if value == "" {
value = "false"
return nil
}
boolVal, err := strconv.ParseBool(value)
if err == nil {
Expand All @@ -330,7 +326,7 @@ func setBoolField(value string, field reflect.Value) error {

func setFloatField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0.0"
return nil
}
floatVal, err := strconv.ParseFloat(value, bitSize)
if err == nil {
Expand Down

0 comments on commit 6ef5f77

Please sign in to comment.