// Copyright 2025 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. // Package protobuf provides optional Protocol Buffers rendering and binding for // gin. // // ProtoBuf support is no longer compiled into the core gin module. Import this // package to opt back in: // // import ( // "github.com/gin-gonic/gin" // "github.com/gin-gonic/gin/render/protobuf" // ) // // protobuf.Render(c, http.StatusOK, msg) // write a ProtoBuf response // protobuf.ShouldBind(c, msg) // decode a ProtoBuf request body // // Importing the package registers the binding and renderer for the // "application/x-protobuf" content type so that the content-type negotiation // done by c.ShouldBind and c.Negotiate keeps working. package protobuf import ( "errors" "io" "net/http" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" "google.golang.org/protobuf/proto" ) // MIMEPROTOBUF is the content type handled by this package. const MIMEPROTOBUF = binding.MIMEPROTOBUF var contentType = []string{"application/x-protobuf"} func init() { binding.Register(MIMEPROTOBUF, Binding) render.Register(MIMEPROTOBUF, func(data any) render.Render { return renderer{Data: data} }) } // renderer implements render.Render for ProtoBuf responses. type renderer struct { Data any } var _ render.Render = renderer{} // Render marshals the data as ProtoBuf and writes it with the ProtoBuf content // type. The data must implement proto.Message. func (r renderer) Render(w http.ResponseWriter) error { r.WriteContentType(w) bytes, err := proto.Marshal(r.Data.(proto.Message)) if err != nil { return err } _, err = w.Write(bytes) return err } // WriteContentType writes the ProtoBuf content type. func (r renderer) WriteContentType(w http.ResponseWriter) { writeContentType(w, contentType) } // Render writes obj to the response as ProtoBuf with status code. It is the // drop-in replacement for the former c.ProtoBuf(code, obj). func Render(c *gin.Context, code int, obj any) { c.Render(code, renderer{Data: obj}) } func writeContentType(w http.ResponseWriter, value []string) { header := w.Header() if val := header["Content-Type"]; len(val) == 0 { header["Content-Type"] = value } } // Binding decodes ProtoBuf request bodies. It can be passed to // c.ShouldBindWith / c.MustBindWith. var Binding binding.BindingBody = protobufBinding{} type protobufBinding struct{} func (protobufBinding) Name() string { return "protobuf" } func (b protobufBinding) Bind(req *http.Request, obj any) error { buf, err := io.ReadAll(req.Body) if err != nil { return err } return b.BindBody(buf, obj) } func (protobufBinding) BindBody(body []byte, obj any) error { msg, ok := obj.(proto.Message) if !ok { return errors.New("obj is not ProtoMessage") } if err := proto.Unmarshal(body, msg); err != nil { return err } // Here it's same to return binding.Validate(obj), but until now we can't // add `binding:""` to the struct which is automatically generated by // gen-proto. return nil } // Bind binds the ProtoBuf request body to obj, aborting with HTTP 400 on error. func Bind(c *gin.Context, obj any) error { return c.MustBindWith(obj, Binding) } // ShouldBind binds the ProtoBuf request body to obj without aborting. func ShouldBind(c *gin.Context, obj any) error { return c.ShouldBindWith(obj, Binding) }