gin

package
v3.15.0+incompatible Latest Latest
Warning

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

Go to latest
Published: Jan 25, 2018 License: Apache-2.0, Apache-2.0 Imports: 16 Imported by: 18

Documentation

Overview

Some convenient utility for usage of gin framework

JSON service

ginConfig := &GinConfig{
	Mode: gin.ReleaseMode,
	Host: "localhost",
	Port: 8080,
}
engine := NewDefaultJsonEngine(ginConfig)

// Start service
// StartServiceOrExit(engine, ginConfig)

// Binds the engine into existing HTTP service
http.Handle("/root-service", engine)

Panic in Code

By using of "NewDefaultJsonEngine()", any panic code would be output as:

{
	"http_status": 500,
	"error_code": -1,
	"error_message": fmt.Sprintf("%v", panicObject),
}

And the HTTP engine would keep running.

Special type of panic object

By "DefaultPanicProcessor()", some types of object, which is panic, would be treat specially:

ValidationError - Generated by "ConformAndValidateStruct()", gives "400 Bad Request" and JSON:

{
	"http_status": 400,
	"error_code": -1,
	"error_message": errObject.Error(),
}

BindJsonError - Generated by "BindJson()", gives "400 Bad Request" and JSON:

{
	"http_status": 400,
	"error_code": -101,
	"error_message": errObject.Error(),
}

DataConflictError - You could panic DataConflictError, which would be output as JSON:

{
	"http_status": 409
	"error_code": errObject.ErrorCode,
	"error_message": errObject.ErrorMessage,
}

NotFound 404

When not found occurs, output following JSON:

{
	"http_status": 404,
	"error_code": -1,
	"uri": c.Request.RequestURI,
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func BindJson

func BindJson(context *gin.Context, object interface{})

Binds JSON and panic with JsonBindError if there is error

func BuildJsonPanicProcessor

func BuildJsonPanicProcessor(panicProcessor PanicProcessor) gin.HandlerFunc

Builds a gin.HandlerFunc, which is used to handle not-nil object of panic

func ConformAndValidateStruct

func ConformAndValidateStruct(object interface{}, v *validator.Validate)

Conforms the object and then validates the object, if any error occurs, panic with validation error.

See leebeson/conform: https://github.com/leebenson/conform See go-playground/validator: https://godoc.org/gopkg.in/go-playground/validator.v9

func DefaultPanicProcessor

func DefaultPanicProcessor(c *gin.Context, panicObject interface{})

Process various of panic object with corresponding HTTP status

ValidationError - Use http.StatusBadRequest as output status

BindJsonError - Use http.StatusBadRequest as output status

Otherwise, use http.StatusInternalServerError as output status

func HeaderWithPaging

func HeaderWithPaging(context *gin.Context, paging *model.Paging)

HeaderWithPaging would set headers with information of paging

func JsonConflictHandler

func JsonConflictHandler(c *gin.Context, body interface{})

Output http.StatusConflict as JSON.

func JsonNoMethodHandler

func JsonNoMethodHandler(c *gin.Context)

Output http.StatusMethodNotAllowed as JSON;

{
	"http_status": 405,
	"error_code": -1,
	"method": c.Request.Method,
	"uri": c.Request.RequestURI,
}

func JsonNoRouteHandler

func JsonNoRouteHandler(c *gin.Context)

Output http.StatusNotFound as JSON;

{
	"http_status": 404,
	"error_code": -1,
	"uri": c.Request.RequestURI,
}

func NewDefaultJsonEngine

func NewDefaultJsonEngine(config *GinConfig) *gin.Engine

Initialize a router with default JSON response

  1. The panic code would not cause process to dead
  2. Use gin-contrib/cors as middleware for cross-site issue
  3. Change (*gin.Engine).NoRoute() with JSON output
  4. Change (*gin.Engine).NoMethod() with JSON output

CORS Setting

	Access-Control-Allow-Origin: *
	Access-Control-Allow-Credentials: true
	Access-Control-Allow-Headers: Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cache-Control, X-Requested-With,
		accept, origin,
		page-size, page-pos, order-by, page-ptr, previous-page, next-page, page-more, total-count
	Access-Control-Allow-Methods: POST, OPTIONS, GET, PUT
 Access-Control-Max-Age": "43200"
Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"time"

	sjson "github.com/bitly/go-simplejson"
	"github.com/gin-gonic/gin"
	"gopkg.in/go-playground/validator.v9"
	"gopkg.in/h2non/gentleman.v2"

	ocheck "github.com/fwtpe/owl-backend/common/testing/check"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	ch "gopkg.in/check.v1"
)

var _ = Describe("Redirect http.Server(listening) to gin engine", func() {
	Context("Service uses \"net/http\", we redirect the implmentation to Gin engine", func() {
		sampleBody := `Hello World!!`

		BeforeEach(func() {
			/**
			 * The gin engine used to process real request of HTTP.
			 */
			sampleGin := NewDefaultJsonEngine(&GinConfig{Mode: gin.ReleaseMode})
			sampleGin.GET("/", func(context *gin.Context) {
				context.String(http.StatusOK, sampleBody)
			})
			// :~)

			sampleHandler := func(resp http.ResponseWriter, req *http.Request) {
				// Delegates to Gin engine
				sampleGin.ServeHTTP(resp, req)

				/**
				 * Ordinal code of http handler
				 */
				//resp.Header().Add("Content-Type", "text/plain")
				//resp.Write([]byte(sampleBody))
				// :~)
			}

			go http.ListenAndServe(
				":20301", http.HandlerFunc(sampleHandler),
			)

			time.Sleep(2 * time.Second)
		})

		It("Should be 200 status and the body must be \"Hello World!!\"", func() {
			resp, err := gentleman.New().URL("http://127.0.0.1:20301").
				Get().
				Send()

			Expect(err).To(Succeed())
			Expect(resp.StatusCode).To(Equal(http.StatusOK))

			Expect(resp.String()).To(Equal(sampleBody))
		})
	})
})

type TestConfigSuite struct{}

var _ = ch.Suite(&TestConfigSuite{})

// Tests the JSON engine for CORS and exception handler, etc.
func (suite *TestConfigSuite) TestNewDefaultJsonEngine(c *ch.C) {
	testCases := []*struct {
		req        *http.Request
		assertFunc func(*ch.C, *httptest.ResponseRecorder, ch.CommentInterface)
	}{
		{ // Tests the CORS
			httptest.NewRequest(http.MethodOptions, "/simple-1", nil),
			func(c *ch.C, resp *httptest.ResponseRecorder, comment ch.CommentInterface) {
				c.Assert(resp.Header().Get("Access-Control-Allow-Origin"), ch.Equals, "*", comment)
			},
		},
		{ // Tests the error of json binding
			httptest.NewRequest(http.MethodPost, "/json-error-1", nil),
			func(c *ch.C, resp *httptest.ResponseRecorder, comment ch.CommentInterface) {
				c.Assert(resp.Code, ch.Equals, http.StatusBadRequest, comment)

				jsonResult, err := sjson.NewFromReader(resp.Body)
				c.Assert(err, ch.IsNil)

				c.Assert(jsonResult.Get("error_code").MustInt(), ch.Equals, -101, comment)
				c.Assert(jsonResult.Get("error_message").MustString(), ch.Equals, "EOF", comment)
			},
		},
		{ // Tests the error of validation
			httptest.NewRequest(http.MethodGet, "/validation-error-1", nil),
			func(c *ch.C, resp *httptest.ResponseRecorder, comment ch.CommentInterface) {
				c.Assert(resp.Code, ch.Equals, http.StatusBadRequest, comment)

				jsonResult, err := sjson.NewFromReader(resp.Body)
				c.Assert(err, ch.IsNil)

				c.Assert(jsonResult.Get("error_code").MustInt(), ch.Equals, -1, comment)
				c.Assert(jsonResult.Get("error_message").MustString(), ch.Matches, ".*Error:Field validation.*", comment)
			},
		},
		{ // Tests the error of panic
			httptest.NewRequest(http.MethodGet, "/panic-1", nil),
			func(c *ch.C, resp *httptest.ResponseRecorder, comment ch.CommentInterface) {
				c.Assert(resp.Code, ch.Equals, http.StatusInternalServerError, comment)

				jsonResult, err := sjson.NewFromReader(resp.Body)
				c.Assert(err, ch.IsNil)

				c.Assert(jsonResult.Get("error_code").MustInt(), ch.Equals, -1, comment)
				c.Assert(jsonResult.Get("error_message").MustString(), ch.Equals, "HERE WE PANIC!!", comment)
			},
		},
		{ // Tests the error of not-found
			httptest.NewRequest(http.MethodGet, "/not-found", nil),
			func(c *ch.C, resp *httptest.ResponseRecorder, comment ch.CommentInterface) {
				c.Assert(resp.Code, ch.Equals, http.StatusNotFound, comment)

				jsonResult, err := sjson.NewFromReader(resp.Body)
				c.Assert(err, ch.IsNil)

				c.Assert(jsonResult.Get("error_code").MustInt(), ch.Equals, -1, comment)
				c.Assert(jsonResult.Get("uri").MustString(), ch.Equals, "/not-found", comment)
			},
		},
	}

	engine := NewDefaultJsonEngine(&GinConfig{Mode: gin.ReleaseMode})
	engine.GET("/sample-1", func(context *gin.Context) {
		context.String(http.StatusOK, "OK")
	})
	engine.POST("/json-error-1", func(context *gin.Context) {
		type car struct {
			Name string `json:"name"`
			Age  int    `json:"age"`
		}

		BindJson(context, &car{})
	})
	engine.GET("/validation-error-1", func(context *gin.Context) {
		type car struct {
			Name string `validate:"min=10"`
		}

		ConformAndValidateStruct(&car{"cc"}, validator.New())
	})
	engine.GET("/panic-1", func(context *gin.Context) {
		panic("HERE WE PANIC!!")
	})

	for i, testCase := range testCases {
		comment := ocheck.TestCaseComment(i)
		ocheck.LogTestCase(c, testCase)

		testCase.req.Header.Set("Origin", "http://non-local/")

		resp := httptest.NewRecorder()
		engine.ServeHTTP(resp, testCase.req)

		testCase.assertFunc(c, resp, comment)
	}
}

func main() {
	engine := NewDefaultJsonEngine(&GinConfig{Mode: gin.ReleaseMode})

	engine.GET(
		"/car/:car_id",
		func(c *gin.Context) {
			c.String(http.StatusOK, "Car Id: "+c.Param("car_id"))
		},
	)

	req := httptest.NewRequest(http.MethodGet, "/car/91", nil)
	resp := httptest.NewRecorder()

	engine.ServeHTTP(resp, req)

	fmt.Println(resp.Body)

}
Output:

Car Id: 91

func OutputJsonIfNotNil

func OutputJsonIfNotNil(c *gin.Context, checkedObject interface{})

Output JSON if the checkedObject is not nil.

If the checkedObject is nil value, calls "JsonNoRouteHandler"

func PagingByHeader

func PagingByHeader(context *gin.Context, defaultPaging *model.Paging) *model.Paging

PagingByHeader would initialize paging object by header

This funcion would load header value:

"page-size" - The size of page
"page-pos" - The position of page, starting with "1"
"order-by" - The order for paging

	<prop_1>#<dir>:<prop_2>#<dir>:...

func ParseOrderBy

func ParseOrderBy(headerValueOfOrderBy string) ([]*model.OrderByEntity, error)

func StartServiceOrExit

func StartServiceOrExit(router *gin.Engine, config *GinConfig)

Try to start the engine with configuration of gin

If some error happened, exit application with "os.Exit(1)"

Types

type BindJsonError

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

Defines the error while binding json

func (BindJsonError) Error

func (err BindJsonError) Error() string

Implements error interface

type DataConflictError

type DataConflictError struct {
	ErrorCode    int32
	ErrorMessage string
}

Defines the error used to represent the "409 Conflict" error

Usage

Case 1: The conflict of unique key on a new or modified data

Case 2: The conflict of complex logical of business on modified data

HTTP Specification

Following paragraph comes from RFC-2616(HTTP/1.1)

The request could not be completed due to a conflict with the current state of the resource.
This code is only allowed in situations where it is expected that
the user might be able to resolve the conflict and resubmit the request.

The response body SHOULD include enough information for the user to
recognize the source of the conflict.
Ideally, the response entity would include enough information for the user or
user agent to fix the problem; however,
that might not be possible and is not required.

Conflicts are most likely to occur in response to a PUT request.
For example, if versioning were being used and the entity being PUT included changes to
a resource which conflict with those made by an earlier (third-party) request,
the server might use the 409 response to indicate that it can't complete the request.

In this case, the response entity would likely contain a list of
the differences between the two versions in a format defined by the response Content-Type.

See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

Example
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"net/http/httptest"

	. "gopkg.in/check.v1"
)

type TestErrorHandlerSuite struct{}

var _ = Suite(&TestErrorHandlerSuite{})

func main() {
	engine := NewDefaultJsonEngine(&GinConfig{Mode: gin.ReleaseMode})
	engine.GET(
		"/data-conflict",
		func(c *gin.Context) {
			panic(DataConflictError{33, "Sample Conflict"})
		},
	)

	req := httptest.NewRequest(http.MethodGet, "/data-conflict", nil)
	resp := httptest.NewRecorder()

	engine.ServeHTTP(resp, req)

	fmt.Println(resp.Body)

}
Output:

{"error_code":33,"error_message":"Sample Conflict","http_status":409}

func (DataConflictError) Error

func (e DataConflictError) Error() string

Implements the error interface

func (DataConflictError) MarshalJSON

func (e DataConflictError) MarshalJSON() ([]byte, error)

Marshal this type of error to:

{
	"http_status": 409,
	"error_code": e.ErrorCode,
	"error_message": e.ErrorMessage,
}

type GinConfig

type GinConfig struct {
	// The mode of gin framework
	// 	const (
	// 		DebugMode   string = "debug"
	// 		ReleaseMode string = "release"
	// 		TestMode    string = "test"
	// 	)
	Mode string
	// The host could be used to start service(optional)
	Host string
	// The post could be used to start service(optional)
	Port uint16
}

Configuration defines the properties on gin framework

func (*GinConfig) GetAddress

func (config *GinConfig) GetAddress() string

Gets the address of url

func (*GinConfig) String

func (config *GinConfig) String() string

Same as "GetAddress()"

type PanicProcessor

type PanicProcessor func(c *gin.Context, panic interface{})

This callback function is used to process panic object

type QueryWrapper

type QueryWrapper gin.Context

QueryWrapper for gin.Context

You may like to use "gin/mvc" framework instead of this utility to convert data by yourself.

func NewQueryWrapper

func NewQueryWrapper(context *gin.Context) *QueryWrapper

NewQueryWrapper converts *gin.Context to *QueryWrapper

func (*QueryWrapper) GetBool

func (wrapper *QueryWrapper) GetBool(key string) *ViableParamValue

GetBool gets query parameter as bool

It accepts: 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False.

func (*QueryWrapper) GetBoolDefault

func (wrapper *QueryWrapper) GetBoolDefault(key string, defaultValue bool) *ViableParamValue

GetBoolDefault gets query parameter as bool with default value

func (*QueryWrapper) GetFloat64

func (wrapper *QueryWrapper) GetFloat64(key string) *ViableParamValue

GetFloat64 gets query parameter as uint64

func (*QueryWrapper) GetFloat64Default

func (wrapper *QueryWrapper) GetFloat64Default(key string, defaultValue float64) *ViableParamValue

GetFloat64Default gets query parameter as uint64 with default value

func (*QueryWrapper) GetInt64

func (wrapper *QueryWrapper) GetInt64(key string) *ViableParamValue

GetInt64 gets query parameter as int64

func (*QueryWrapper) GetInt64Default

func (wrapper *QueryWrapper) GetInt64Default(key string, defaultValue int64) *ViableParamValue

GetInt64Default gets query parameter as int64 with default value

func (*QueryWrapper) GetUint64

func (wrapper *QueryWrapper) GetUint64(key string) *ViableParamValue

GetUint64 gets query parameter as uint64

func (*QueryWrapper) GetUint64Default

func (wrapper *QueryWrapper) GetUint64Default(key string, defaultValue uint64) *ViableParamValue

GetUint64Default gets query parameter as uint64 with default value

type ValidationError

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

Used to be handled globally

func (ValidationError) Error

func (err ValidationError) Error() string

Implements error interface

type ViableParamValue

type ViableParamValue struct {
	// Whether or not the parameter is viable(not empty)
	Viable bool
	// The value of parameter
	Value interface{}
	// The error of processing value of parameter
	Error error
}

ViableParamValue defines the value of param and whether or not the value is viable

In order to indicate the value of parameter of bool, integer... , this object has a boolean filed to indicate whether or not the value is sensible.

Directories

Path Synopsis
A MVC binder for free-style of function handler with *gin.Context Abstract There are may tedious processes for coding on web service: 1.
A MVC binder for free-style of function handler with *gin.Context Abstract There are may tedious processes for coding on web service: 1.

Jump to

Keyboard shortcuts

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