xgin

package module
v1.1.2 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2026 License: MIT Imports: 10 Imported by: 0

README

xgin

xgin is a pragmatic extension toolkit for gin. It keeps Gin lightweight while adding typed handlers, multi-source binding, recursive business validation, and production-friendly middleware.

Install

go get github.com/lazyboon/xgin

Core Features

  • Generic handler wrapper: xgin.Handle(func(ctx *xgin.Context, req *Req) xgin.Response)
  • Multi-source auto binding: URI + Query + Header + Body in a single flow (AutoBind)
  • Recursive business validation with IValidator
  • Unified chain-style response builder with multiple content types
  • Global lifecycle hook points for bind errors and response processing

Quick Start

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/lazyboon/xgin"
)

type LoginReq struct {
	User string `json:"user" binding:"required"`
	Pass string `json:"pass" binding:"required"`
}

func main() {
	r := gin.New()

	r.POST("/login", xgin.Handle(func(ctx *xgin.Context, req *LoginReq) xgin.Response {
		return xgin.Response{}.
			WithHttpStatus(http.StatusOK).
			WithCode(0).
			WithMsg("ok").
			WithData(gin.H{"user": req.User})
	}))

	_ = r.Run(":8080")
}

Validator Features

Validation in xgin runs in two stages:

  1. Gin tag validation (binding:"required", etc.)
  2. Recursive custom business validation via Validate() error (IValidator)
Recursive Coverage
  • Nested structs
  • Slices and arrays ([]T, [N]T)
  • Maps (map[K]V, validates both keys and values)
  • Pointer receiver and value receiver validators
  • Built-in recursion depth guard (default: 20)
Example
type Item struct {
	Price int `json:"price" binding:"required"`
}

func (i Item) Validate() error {
	if i.Price <= 0 {
		return errors.New("price must be positive")
	}
	return nil
}

type CreateOrderReq struct {
	UserID string `uri:"user_id" binding:"required"`
	Token  string `header:"X-Token" binding:"required"`
	Page   int    `form:"page"`
	Items  []Item `json:"items" binding:"required"`
}

func (r *CreateOrderReq) Validate() error {
	if len(r.Items) == 0 {
		return errors.New("items cannot be empty")
	}
	return nil
}

Inside xgin.Handle(...), AutoBind(&req) performs:

  • Silent fill for URI/Query/Header
  • Body bind with Gin tag validation
  • Recursive business validation (IValidator)
Depth Configuration
xgin.SetValidatorMaxDepth(64)

Middleware Features

1) Metadata Middleware

middleware.NewMetadata(...) injects runtime metadata into response headers:

  • Request ID (default: X-Request-ID)
  • Receive time (default: X-Receive-Time)
  • Response time (default: X-Response-Time)
  • Custom static headers
r.Use(middleware.NewMetadata(&middleware.MetadataOptions{
	NeedRequestID:    true,
	NeedReceiveTime:  true,
	NeedResponseTime: true,
	Custom: map[string]string{
		"X-Service": "order-api",
	},
}))
2) AccessLog Middleware

middleware.NewAccessLog(handler, ...) provides structured access logging:

  • Optional capture of request/response headers and bodies
  • Route-level overrides via SpecificPath
  • Path skip list via SkipPaths
  • Safe request body capture limit to reduce OOM risk
  • Request ID pass-through or auto-generation
  • Binary response detection to avoid log pollution
r.Use(middleware.NewAccessLog(func(e *middleware.Entity) {
	// send to zap/slog/ELK
}, &middleware.AccessLogOptions{
	BaseOption: &middleware.AccessLogBaseOptions{
		RequestHeader:  true,
		RequestBody:    true,
		ResponseHeader: true,
		ResponseBody:   true,
		MaxBodyLength:  4096,
	},
	SkipPaths: []string{"GET:/ping"},
	SpecificPath: map[string]*middleware.AccessLogBaseOptions{
		"POST:/login": {
			RequestHeader:  true,
			RequestBody:    false,
			ResponseHeader: true,
			ResponseBody:   false,
			MaxBodyLength:  1024,
		},
	},
}))
3) Recovery Middleware

middleware.NewRecovery(...) provides panic recovery for production:

  • Broken pipe / connection reset detection
  • Configurable stack depth and frame skip
  • Sensitive header masking
  • Pluggable logging callback
  • Custom panic response handler
r.Use(middleware.NewRecovery(&middleware.RecoveryOptions{
	LogCallback: func(c *gin.Context, msg string) {
		// write msg to logger
	},
	Handler: func(c *gin.Context, err any) {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "internal error"})
	},
	MaxStack:  50,
	StackSkip: 3,
}))

Hookers

You can customize xgin lifecycle globally:

xgin.SetHookerBindingError(func(ctx *gin.Context, err error) {
	ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
})

xgin.SetHookerTransformResponse(func(r xgin.Response) any {
	return gin.H{"code": r.Code, "msg": r.Msg, "data": r.Data}
})

xgin.SetHookerBeforeResponse(func(ctx *gin.Context, r xgin.Response) bool {
	return false // return true to abort render
})

xgin.SetHookerAfterResponse(func(ctx *gin.Context, r xgin.Response) {
	// audit/log
})

Response Content Types

Response.WithContentType(...) supports:

  • JSON / IndentedJSON / SecureJSON / JSONP / AsciiJSON / PureJSON
  • MsgPack / ProtoBuf / TOML / XML / YAML
  • String / Redirect / HTML

Development

go test ./...
go vet ./...
go build ./...

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ValidatorMaxDepth defines the recursion limit for the validation engine.
	ValidatorMaxDepth = 20

	// ErrMaxDepth is returned when the validation exceeds ValidatorMaxDepth.
	ErrMaxDepth = errors.New("xgin.validator: maximum recursion depth exceeded")

	// HookerBindingError is triggered on AutoBind or IValidator failures.
	HookerBindingError func(ctx *gin.Context, err error)

	// HookerTransformResponse modifies the Response object before final rendering.
	HookerTransformResponse func(response Response) any

	// HookerBeforeResponse executes before rendering; return true to abort.
	HookerBeforeResponse func(ctx *gin.Context, response Response) (stop bool)

	// HookerAfterResponse executes after the response has been written.
	HookerAfterResponse func(ctx *gin.Context, response Response)
)

Global Configuration and Lifecycle Hooks

Functions

func Handle

func Handle[T any](h HandlerFunc[T]) gin.HandlerFunc

Handle wraps business logic with automated binding, deep validation, and lifecycle hooks.

func SetHookerAfterResponse

func SetHookerAfterResponse(h func(ctx *gin.Context, response Response))

func SetHookerBeforeResponse

func SetHookerBeforeResponse(h func(ctx *gin.Context, response Response) (stop bool))

func SetHookerBindingError

func SetHookerBindingError(h func(ctx *gin.Context, err error))

func SetHookerTransformResponse

func SetHookerTransformResponse(h func(response Response) any)

func SetValidatorMaxDepth

func SetValidatorMaxDepth(depth int)

func Wrap

func Wrap(handler func(ctx *Context) Response) gin.HandlerFunc

Wrap is a lightweight wrapper for handlers that do not require a request DTO.

Types

type ContentType

type ContentType int8
const (
	ContentTypeJSON ContentType = iota
	ContentTypeIndentedJSON
	ContentTypeSecureJSON
	ContentTypeJsonpJSON
	ContentTypeAsciiJSON
	ContentTypePureJSON
	ContentTypeMsgPack
	ContentTypeProtoBuf
	ContentTypeRedirect
	ContentTypeString
	ContentTypeTOML
	ContentTypeXML
	ContentTypeYAML
	ContentTypeHTML
)

type Context

type Context struct {
	*gin.Context
	// contains filtered or unexported fields
}

Context wraps gin.Context to provide extended functionality

func NewContext

func NewContext(c *gin.Context) *Context

NewContext creates a new xgin Context from a gin Context

func (*Context) AutoBind

func (c *Context) AutoBind(obj any) error

AutoBind handles multi-source parameter binding and dual-stage validation. It follows a "Silent Fill -> Final Bind -> Recursive Validate" pattern to ensure all fields (Header/URI/Query/Body) are populated before global validation.

type HandlerFunc

type HandlerFunc[T any] func(ctx *Context, req *T) Response

HandlerFunc defines the signature for type-safe generic handlers with request DTO.

type IValidator

type IValidator interface {
	Validate() error
}

IValidator defines the interface for custom business logic validation.

type Response

type Response struct {
	HttpStatus  int               `json:"-"`
	Code        int               `json:"code"`
	Msg         string            `json:"msg"`
	Data        any               `json:"data"`
	Header      map[string]string `json:"-"`
	Errors      []error           `json:"-"`
	ContentType ContentType       `json:"-"`
	HtmlPath    string            `json:"-"`
}

func (Response) WithCode

func (r Response) WithCode(code int) Response

func (Response) WithContentType

func (r Response) WithContentType(typ ContentType) Response

func (Response) WithData

func (r Response) WithData(data any) Response

func (Response) WithError

func (r Response) WithError(err error) Response

func (Response) WithErrors

func (r Response) WithErrors(errors ...error) Response

func (Response) WithHeader

func (r Response) WithHeader(key, val string) Response

func (Response) WithHeaders

func (r Response) WithHeaders(kv map[string]string) Response

func (Response) WithHtmlPath

func (r Response) WithHtmlPath(htmlPath string) Response

func (Response) WithHttpStatus

func (r Response) WithHttpStatus(status int) Response

func (Response) WithMsg

func (r Response) WithMsg(msg string) Response

type Validator

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

Validator handles recursive validation with metadata caching.

func NewValidator

func NewValidator(maxDepth int) *Validator

NewValidator creates a new validator instance with a specified depth limit.

func SharedValidator

func SharedValidator(maxDepth int) *Validator

SharedValidator returns a global singleton validator.

func (*Validator) Validate

func (v *Validator) Validate(obj any) (err error)

Validate triggers the recursive validation process for the given object.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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