Help us shape the future of Go! Take the 2026 Go Developer Survey

ginfmt

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Dec 19, 2020 License: MIT Imports: 5 Imported by: 0

README

GINFMT

Build Status Go Report Card GoDoc MIT license

Go version >=1.13

go get github.com/sleagon/ginfmt

Usage

ginfmt is a simple toolkit to format response of gin server.

Notice:

The ErrGen is a simple function in version 1.0.3, which is complicated to validate whether a error is wrapped from a known ErrGen. So we just redesigned the ErrGen to a struct, this struct will help us to do the magic quite easily.

Upgrade:

Just replace all XXXErr() with XXXErr.Gen.

Example
// default logger(logrus) and default translator (echo)
ginfmt.Init(nil, nil)
BadRequest := errfmt.Register(http.StatusNotFound, 10010, "record not found")


// normal response
ginfmt.Data(c, "foo")
{
	"code": 0,
	"messsage": "ok",
	"data": "foo"
}
// abnormal response
ginfmt.Error(c, BadRequest.Gen())
{
	"code": 10010,
	"message": "record not found",
	"data": nil
}
// abnormal response with payload
ginfmt.DataError(c, "foo", BadRequest.Gen())
{
	"code": 10010,
	"message": "record not found",
    "data": "foo"
}

I18N

Most of time, error message should be translated to perticular language. You need define a translator like this:

func DemoTrans(ctx context.Context, locale string, key string) string {
	demoMap := map[string]map[string]string{
		"zh": map[string]string{
			"foo": "这是一个foo信息",
		},
		"en-US": map[string]string{
			"foo": "This is foo message",
		},
	}
	return demoMap[locale][key]
}

func TestI18n(t *testing.T) {
	Init(nil, DemoTrans)
	FooError := errfmt.Register(http.StatusNotFound, 10010, "foo")
	r := gin.Default()
	r.Use(func(c *gin.Context) {
		c.Request.Header.Set("locale", "zh")
	})
	r.Use(MW())
	r.GET("/ginfmt", func(c *gin.Context) {
		DataError(c, "bar", FooError.Gen())
	})
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/ginfmt", nil)
	r.ServeHTTP(w, req)
	resp := new(Resp3)
	assert.Nil(t, json.Unmarshal(w.Body.Bytes(), resp))
	assert.Equal(t, resp.Code, FooError.Gen().Code())
	assert.Equal(t, resp.Message, FooError.Gen().Message(context.TODO(), "zh"))
	assert.Equal(t, "bar", resp.Data)
}

ginfmt will read "locale" from query/header/cookie/gin.Context,you may need set this value first.

Logger

All response handled by ginfmt will be logged according to level of error, by default, error whose http status code is less than 500 will be recorded as information, other errors will be recorded as error.

All error returned to users should be pre defined before used.

    // default level
	FooError := errfmt.Register(http.StatusNotFound, 10010, "foo message")
	// custom level
	FooError := errfmt.Register(http.StatusNotFound, 10010, "foo message", errfmt.LevelError)

Wrapped error

You may need to add extra log info to error, thanks to errors.Unwrap and fmt.Errorf("%w, dome extra info", err) introduced in go 1.13, we can easily do this.

func TestWrappedError(t *testing.T) {
	FooError := errfmt.Register(http.StatusNotFound, 10010, "foo message")
	r := gin.Default()
	r.Use(MW())
	r.GET("/ginfmt", func(c *gin.Context) {
		// YOUR ROUTER CODE
		err := fmt.Errorf("%w, extra info: test info", FooError.Gen())
		Error(c, err)
	})
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/ginfmt", nil)
	r.ServeHTTP(w, req)
	resp := new(Resp2)
	assert.Nil(t, json.Unmarshal(w.Body.Bytes(), resp))
	assert.Equal(t, resp.Code, FooError.Gen().Code())
	assert.Equal(t, resp.Message, FooError.Gen().Message(context.TODO(), ""))
	assert.Equal(t, 0, resp.Data)
}

[New] IS

You may familiar with github.com/pkg/errors or errors package after 1.17 which provide errors.Is method to judge whether the error is wrapped from another error. In order to check whether a error is generated from a known ErrGen, we add a new method Is to realize that.

func TestErrorIs(t *testing.T) {
	infoErr := Register(http.StatusOK, 20001, "%v is a invalid name")
	err := infoErr.Gen("foo")
	nerr := fmt.Errorf("%w balabalababa info", err)
	assert.Equal(t, true, infoErr.Is(nerr), "")
}

A runnable example

Here is a runnable example.

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/sleagon/ginfmt"
	"github.com/sleagon/ginfmt/errfmt"
)

var (
	BadRequest = errfmt.Register(http.StatusBadRequest, 10001, "Params is invalid")
)

func main() {
	ginfmt.Init(nil, nil)
	r := gin.Default()
	r.Use(ginfmt.MW())
	r.GET("/bad", func(c *gin.Context) {
		ginfmt.Error(c, BadRequest.Gen())
	})
	r.GET("/ping", func(c *gin.Context) {
		ginfmt.Data(c, "pong")
	})
	r.GET("/bad_payload", func(c *gin.Context) {
		// do sth
		err := fmt.Errorf("this is not a valid phone num %w", BadRequest.Gen())
		ginfmt.DataError(c, gin.H{"phone": "invalid", "email": "valid"}, err)
	})

	r.Run() // listen and serve on 0.0.0.0:8080
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Data

func Data(c *gin.Context, data interface{})

Data set data field of response

func DataError

func DataError(c *gin.Context, data interface{}, err error)

DataError Set response data and error at the same time.

func Error

func Error(c *gin.Context, err error)

Error set response error

func Init

func Init(log Logger, trans errfmt.Translator)

Init init ginfmt with log and i18n translator

func MW

func MW() gin.HandlerFunc

MW core middleware to format the response

func NewHandlerFunc added in v1.1.1

func NewHandlerFunc(h Handler) gin.HandlerFunc

NewHandlderFunc build a gin#HandlerFunc based on Handler

Types

type Handler added in v1.1.1

type Handler func(c *gin.Context) (interface{}, error)

Handler ginfmt handler the interface{} will be set to data field in response body the error will be transformed to some specific response body

type Logger

type Logger interface {
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	Warn(args ...interface{})
	Warnf(format string, args ...interface{})
	Info(args ...interface{})
	Infof(format string, args ...interface{})
	Debug(args ...interface{})
	Debugf(format string, args ...interface{})
}

type Resp

type Resp struct {
	Code    int
	Message string
	Data    interface{}
}

Resp data template

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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