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 ¶
- func BindJson(context *gin.Context, object interface{})
- func BuildJsonPanicProcessor(panicProcessor PanicProcessor) gin.HandlerFunc
- func ConformAndValidateStruct(object interface{}, v *validator.Validate)
- func DefaultPanicProcessor(c *gin.Context, panicObject interface{})
- func HeaderWithPaging(context *gin.Context, paging *model.Paging)
- func JsonConflictHandler(c *gin.Context, body interface{})
- func JsonNoMethodHandler(c *gin.Context)
- func JsonNoRouteHandler(c *gin.Context)
- func NewDefaultJsonEngine(config *GinConfig) *gin.Engine
- func OutputJsonIfNotNil(c *gin.Context, checkedObject interface{})
- func PagingByHeader(context *gin.Context, defaultPaging *model.Paging) *model.Paging
- func ParseOrderBy(headerValueOfOrderBy string) ([]*model.OrderByEntity, error)
- func StartServiceOrExit(router *gin.Engine, config *GinConfig)
- type BindJsonError
- type DataConflictError
- type GinConfig
- type PanicProcessor
- type QueryWrapper
- func (wrapper *QueryWrapper) GetBool(key string) *ViableParamValue
- func (wrapper *QueryWrapper) GetBoolDefault(key string, defaultValue bool) *ViableParamValue
- func (wrapper *QueryWrapper) GetFloat64(key string) *ViableParamValue
- func (wrapper *QueryWrapper) GetFloat64Default(key string, defaultValue float64) *ViableParamValue
- func (wrapper *QueryWrapper) GetInt64(key string) *ViableParamValue
- func (wrapper *QueryWrapper) GetInt64Default(key string, defaultValue int64) *ViableParamValue
- func (wrapper *QueryWrapper) GetUint64(key string) *ViableParamValue
- func (wrapper *QueryWrapper) GetUint64Default(key string, defaultValue uint64) *ViableParamValue
- type ValidationError
- type ViableParamValue
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
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 ¶
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 ¶
HeaderWithPaging would set headers with information of paging
func JsonConflictHandler ¶
Output http.StatusConflict as JSON.
func JsonNoMethodHandler ¶
Output http.StatusMethodNotAllowed as JSON;
{ "http_status": 405, "error_code": -1, "method": c.Request.Method, "uri": c.Request.RequestURI, }
func JsonNoRouteHandler ¶
Output http.StatusNotFound as JSON;
{ "http_status": 404, "error_code": -1, "uri": c.Request.RequestURI, }
func NewDefaultJsonEngine ¶
Initialize a router with default JSON response
- The panic code would not cause process to dead
- Use gin-contrib/cors as middleware for cross-site issue
- Change (*gin.Engine).NoRoute() with JSON output
- 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/Cepave/open-falcon-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 ¶
Output JSON if the checkedObject is not nil.
If the checkedObject is nil value, calls "JsonNoRouteHandler"
func PagingByHeader ¶
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 ¶
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
type DataConflictError ¶
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
type PanicProcessor ¶
This callback function is used to process panic object
type QueryWrapper ¶
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
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.
Source Files ¶
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. |