echo_binder

package module
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 28, 2023 License: GPL-3.0 Imports: 12 Imported by: 0

README

Echo Binder

A custom binder for the Echo web framework that replaces echo's DefaultBinder. This one supports the same syntax as gongular's binder and uses validator to validate the binded structs.

Most of the time, most of your echo request handlers will start/be filled with binding the body & query data into structures, parsing the headers/path parameters and create a lot of boiler-plate for no reason. This binder aims to reduce the repetitive work while making it easy and user friendly by binding the headers, path, query and body parameters into one struct automatically by using the same context.Bind function (without changing your code at all)!

Features

  • Binding URL Query Parameters
  • Binding Path Parameters
  • Binding Headers
  • Binding Body
  • Binding Forms
  • Struct Validation

Usage

Installation

Download echo-binder by using:

get get -u github.com/avivatedgi/echo-binder

And import following in your code:

import "github.com/avivatedgi/echo-binder"

Wherever you initiate your echo engine, just insert the following line:

e := echo.New()

// Set the echo's binder to use echo-binder instead the DefaultBinder
e.Binder = echo_binder.New()
URL Query Parameters

Query parameters are optional key-value pairs that appear to the right of the ? in a URL. For example, the following URL has two query params, sort and page, with respective values ASC and 2:

http://example.com/articles?sort=ASC&page=2

Query parameters are case sensitive, and can only hold primitives and slices of primitives, for example for the following structure:

type QueryExample struct {
    Query struct {
        Id      string   `binder:"id"`
        Values  []int    `binder:"values"`
    }
}

And the following URL:

http://example.com/users?id=1234&values=1&values=2&values=3

The struct will be filled with the following values:
func handler(c echo.Context) error {
    var example QueryExample
    if err := c.Bind(&example); err != nil {
        return err
    }

    fmt.Println(example.Query.Id)         // "1234"
    fmt.Println(example.Query.Values)     // ["1", "2", "3"]
}
Path Parameters

Path parameters are variable parts of a URL path. They are typically used to point to a specific resource within a collection, such as a user identified by ID. A URL can have several path parameters, each prefixed with colon :. For example the following URL has two path parameters, userId and postId:

http://example.com/users/:userId/posts/:postId

Path parameters are case sensitive, and can only hold primitives, for example the following structure:

type PathExample struct {
    Path struct {
        UserId      string   `binder:"userId"`
        PostId      string   `binder:"postId"`
    }
}

And the following URL:

http://example.com/users/1234/posts/5678

The struct will be filled with the following values:
func handler(c echo.Context) error {
    var example PathExample
    if err := c.Bind(&example); err != nil {
        return err
    }

    fmt.Println(example.Path.UserId)      // "1234"
    fmt.Println(example.Path.PostId)      // "5678"
}
Headers

HTTP headers let the client and the server pass additional information with an HTTP request or response. HTTP headers names are case insensitive followed by a colon (:), then by its value.

Header values can only hold primitives, for example for the following structure:

type HeaderExample struct {
    Header struct {
        AcceptLanguage  string  `binder:"Accept-Language"`
        UserAgent       string  `binder:"User-Agent"`
    }
}

And the following values:

Accept-Language: en-US,en;q=0.5
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
The struct will be filled with the following values:
func handler(c echo.Context) error {
    var example HeaderExample
    if err := c.Bind(&example); err != nil {
        return err
    }

    fmt.Println(example.Header.AcceptLanguage)    // "en-US,en;q=0.5"
    fmt.Println(example.Header.UserAgent)         // "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0"
}
Body

The type of the body of the request is indicated by the Content-Type header. This functionallity bind the data under the Body attribute under your struct, but the logic here is exactly as in echo's body binder.

Example
type BodyExample struct {
    Body struct {
        Username    string      `json:"username" xml:"username"`
        Password    string      `json:"password" xml:"password"`
    }
}

func handler(c echo.Context) error {
    var example BodyExample
    if err := c.Bind(&example); err != nil {
        return err
    }

    fmt.Println(example.Body.Username)    // avivatedgi
    fmt.Println(example.Body.Password)    // *********
}


The data will be binded according to the specific Content-Type header, if it's application/json it will use the json attributes, if it's application/xml it will use the xml attributes.

Check which body params have been sent

A lot of times programmers want to know which body params have been sent and which are just binded to the default values, echo-binder let's you do it! In order to do it, you just need to declare another sub-structure:

type BodyExample struct {
    Body struct {
        Username    string      `json:"username" xml:"username"`
        Password    string      `json:"password" xml:"password"`
        Nested struct {
            Nested struct {
                Flag bool `json:"flag"`
            } `json:"nested"`
        } `json:"nested"`
    }

    BodySentFields echo_binder.RecursiveLookupTable
}

From now and on, all you need to do is just use the BodyExample.BodySentFields.FieldExists function:

Example
type BodyExample struct {
    Body struct {
        Username    string      `json:"username" xml:"username"`
        Password    string      `json:"password" xml:"password"`
        Nested struct {
            Nested struct {
                Flag bool `json:"flag"`
            } `json:"nested"`
        } `json:"nested"`
    }

    BodySentFields echo_binder.RecursiveLookupTable
}

func handler(c echo.Context) error {
    var example BodyExample
    if err := c.Bind(&example); err != nil {
        return err
    }

    fmt.Println(example.Body.Username)          // avivatedgi
    fmt.Println(example.Body.Password)          // *********
    fmt.Println(example.Nested.Nested.Flag)     // false

    fmt.Println(example.BodySentFields.FieldExists("username"))     // true
    fmt.Println(example.BodySentFields.FieldExists("password"))     // true
    fmt.Println(example.BodySentFields.FieldExists("nested.nested"))     // true
    fmt.Println(example.BodySentFields.FieldExists("nested.nested.flag"))     // true
    fmt.Println(example.BodySentFields.FieldExists("blabla"))     // false
    fmt.Println(example.BodySentFields.FieldExists("nested.blabla"))     // false
}
Forms

Actually, forms are supposed to be also part of the Body binding (in echo they actually are, under the application/x-www-form-urlencoded Content-Type). So binding forms can be used by two ways:

By the Body attribute
type FormBodyExample struct {
    Body struct {
        Username    string  `form:"username"`
        Password    string  `form:"password"`
    }
}

func handler(c echo.Context) error {
    var example FormBodyExample
    if err := c.Bind(&example); err != nil {
        return err
    }

    fmt.Println(example.Body.Username)    // avivatedgi
    fmt.Println(example.Body.Password)    // *********
}
By the Form attribute
type FormExample struct {
    Form struct {
        Username    string  `binder:"username"`
        Password    string  `binder:"password"`
    }
}

func handler(c echo.Context) error {
    var example FormExample
    if err := c.Bind(&example); err != nil {
        return err
    }

    fmt.Println(example.Form.Username)    // avivatedgi
    fmt.Println(example.Form.Password)    // *********
}
Validation

The structs that are binded by this Binder are automatically validated by the validate attribute using the validator package. For more information about the validator check the documentation.

Notes
  • All of the sub-structures in the request (Path, Query, Header, Body, Form) can have embedded struct
  • All of the sub-structures in the request must be struct (except the Body)
  • You can use the default binder of echo in case of errors, so if you already have a code base and you don't want to change all of requests to work this way, just use the binder.CallEchoDefaultBinderOnError(true) function.
  • You can ignore fields by using the binder:"-" tag
  • You can ignore header fields with the value "null" by using the binder.IgnoreNullStringOnHeader(true)

Documentation

Overview

A custom binder for the echo web framework that replaces echo's DefaultBinder. This one supports the same syntax as gongular's binder and uses go-playground/validator to validate the binded structs.

Index

Constants

View Source
const (
	TagIdentifier string = "binder"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Binder

type Binder struct {
	// contains filtered or unexported fields
}

A replacement for the echo.DefaultBinder that binds the Path, Query, Header, Body and Form params into nested structures that passed into the binder, and finally valiate the structure with the go-playground/validator package. For more information about the validator check: https://pkg.go.dev/github.com/go-playground/validator

To use this binder, just add it to the echo.Echo instance:

e := echo.New()
e.Binder = echo_binder.New()

For example, for this struct defined:

type RequestExample struct {
	Body struct {
		Name string `json:"name" validate:"required"`
	}

	Query struct {
		PostId int `binder:"postId" validate:"required"`
	}

	Path struct {
		UserId int `binder:"id" validate:"required"`
	}

	Header struct {
		AcceptLanguage string `binder:"Accept-Language"`
		UserAgent string `binder:"User-Agent"`
	}
}

And this code execution:

func requestHandler(c echo.Context) error {
	user := &RequestExample{}
	if err := binder.Bind(user, c); err != nil {
		return err
	}

	// Do something with the request
}

The binder will bind the following params: From the body, the name field will be bound to the Name field of the struct. From the query, the postId field will be bound to the PostId field of the struct. From the path, the id field will be bound to the UserId field of the struct. From the header, the Accept-Language field will be bound to the AcceptLanguage field of the struct. From the header, the User-Agent field will be bound to the UserAgent field of the struct.

func New

func New() *Binder

func (Binder) Bind

func (binder Binder) Bind(i interface{}, c echo.Context) error

func (*Binder) CallEchoDefaultBinderOnError added in v0.0.4

func (binder *Binder) CallEchoDefaultBinderOnError(value bool)

func (*Binder) IgnoreNullStringOnHeader added in v0.0.5

func (binder *Binder) IgnoreNullStringOnHeader(value bool)

type RecursiveLookupTable added in v0.0.3

type RecursiveLookupTable map[string]RecursiveLookupTable

func (*RecursiveLookupTable) FieldExists added in v0.0.3

func (l *RecursiveLookupTable) FieldExists(key string) bool

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL