github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/ginx/jsoni.go (about)

     1  package ginx
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  
     9  	"github.com/bingoohuang/gg/pkg/jsoni"
    10  	"github.com/bingoohuang/gg/pkg/jsoni/extra"
    11  	"github.com/bingoohuang/gg/pkg/strcase"
    12  	"github.com/gin-gonic/gin"
    13  	"github.com/gin-gonic/gin/binding"
    14  )
    15  
    16  // ShouldBind checks the Content-Type to select a binding engine automatically,
    17  // Depending on the "Content-Type" header different bindings are used:
    18  //
    19  //	"application/json" --> JSON binding
    20  //	"application/xml"  --> XML binding
    21  //
    22  // otherwise --> returns an error
    23  // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
    24  // It decodes the json payload into the struct specified as a pointer.
    25  // Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.
    26  func ShouldBind(c *gin.Context, obj interface{}) error {
    27  	var b binding.Binding
    28  	switch c.ContentType() {
    29  	case binding.MIMEJSON:
    30  		b = JSONBind
    31  	default:
    32  		b = binding.Default(c.Request.Method, c.ContentType())
    33  	}
    34  	return c.ShouldBindWith(obj, b)
    35  }
    36  
    37  var JSONBind = jsoniBinding{}
    38  
    39  type jsoniBinding struct{}
    40  
    41  func (jsoniBinding) Name() string { return "json" }
    42  
    43  func (jsoniBinding) Bind(req *http.Request, obj interface{}) error {
    44  	if req == nil || req.Body == nil {
    45  		return fmt.Errorf("invalid request")
    46  	}
    47  	return decodeJSON(req.Body, obj)
    48  }
    49  
    50  func (jsoniBinding) BindBody(body []byte, obj interface{}) error {
    51  	return decodeJSON(bytes.NewReader(body), obj)
    52  }
    53  
    54  // JsoniConfig tries to be 100% compatible with standard library behavior
    55  var JsoniConfig = jsoni.Config{
    56  	EscapeHTML:             true,
    57  	SortMapKeys:            true,
    58  	ValidateJsonRawMessage: true,
    59  	Int64AsString:          true,
    60  }.Froze()
    61  
    62  func init() {
    63  	JsoniConfig.RegisterExtension(&extra.NamingStrategyExtension{Translate: strcase.ToCamelLower})
    64  }
    65  
    66  func decodeJSON(r io.Reader, obj interface{}) error {
    67  	if err := JsoniConfig.NewDecoder(r).Decode(nil, obj); err != nil {
    68  		return err
    69  	}
    70  	return validate(obj)
    71  }
    72  
    73  func validate(obj interface{}) error {
    74  	validator := binding.Validator
    75  	if validator == nil {
    76  		return nil
    77  	}
    78  	return validator.ValidateStruct(obj)
    79  }
    80  
    81  // JSONRender contains the given interface object.
    82  type JSONRender struct {
    83  	Data     interface{}
    84  	JsoniAPI jsoni.API
    85  }
    86  
    87  var jsonContentType = []string{"application/json; charset=utf-8"}
    88  
    89  // Render (JSON) writes data with custom ContentType.
    90  func (r JSONRender) Render(w http.ResponseWriter) (err error) {
    91  	return WriteJSONOptions(w, r.Data, WithJsoniAPI(r.JsoniAPI))
    92  }
    93  
    94  // WriteContentType (JSON) writes JSON ContentType.
    95  func (r JSONRender) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) }
    96  
    97  type WriteJSONConfig struct {
    98  	JsoniAPI jsoni.API
    99  }
   100  
   101  type WriteJSONConfigFn func(*WriteJSONConfig)
   102  
   103  func WithJsoniAPI(api jsoni.API) WriteJSONConfigFn {
   104  	return func(o *WriteJSONConfig) {
   105  		o.JsoniAPI = api
   106  	}
   107  }
   108  
   109  // WriteJSONOptions marshals the given interface object and writes it with custom ContentType.
   110  func WriteJSONOptions(w http.ResponseWriter, obj interface{}, fns ...WriteJSONConfigFn) error {
   111  	options := &WriteJSONConfig{}
   112  	for _, fn := range fns {
   113  		fn(options)
   114  	}
   115  
   116  	if options.JsoniAPI == nil {
   117  		options.JsoniAPI = JsoniConfig
   118  	}
   119  
   120  	writeContentType(w, jsonContentType)
   121  
   122  	data, err := options.JsoniAPI.Marshal(nil, obj)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	_, err = w.Write(data)
   127  	return err
   128  }
   129  
   130  // WriteJSON marshals the given interface object and writes it with custom ContentType.
   131  func WriteJSON(w http.ResponseWriter, obj interface{}) error {
   132  	writeContentType(w, jsonContentType)
   133  	jsonBytes, err := JsoniConfig.Marshal(nil, obj)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	_, err = w.Write(jsonBytes)
   138  	return err
   139  }
   140  
   141  func writeContentType(w http.ResponseWriter, value []string) {
   142  	header := w.Header()
   143  	if val := header["Content-Type"]; len(val) == 0 {
   144  		header["Content-Type"] = value
   145  	}
   146  }