gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
79.2k stars 8.03k forks source link

Other Serialization formats - Amazon ION #3454

Open BigB84 opened 1 year ago

BigB84 commented 1 year ago

Hi,

Thanks for creating gin, it's awesome!

I'm building RESTFUL API but I'd like use serialization format other than available here. I chose Amazon ION. There is an official go library And even curl-like toll supporting it, see here how it sets application/ion header.

Is it possible?

pscheid92 commented 1 year ago

You could write your own binding and render if you want. I'm by no means an expert and I never used ion before, but this is how I would tackle it:

🐞 I think there is still a bug in the binding code. Or I use ion wrong.

main

type Person struct {
    Name string `ion:"name"`
    Age  int    `ion:"age"`
}

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

    r.GET("/testing", func(c *gin.Context) {
        person := Person{Name: "Jane Doe", Age: 44}
        c.Render(http.StatusOK, Ion(person))
    })

    r.POST("/testing", func(c *gin.Context) {
        var input Person
        if err := c.MustBindWith(&input, IonBinding); err != nil {
            return
        }
        c.String(http.StatusOK, "%s (%d)", input.Name, input.Age)
    })

    if err := r.Run(":8080"); err != nil {
        log.Fatalln(err)
    }
}

binding

type IonBinding struct{}

func (IonBinding) Name() string {
    return "ion"
}

func (IonBinding) Bind(req *http.Request, obj any) error {
    if req == nil || req.Body == nil {
        return errors.New("invalid request")
    }
    return decodeIon(req.Body, obj)
}

func (IonBinding) BindBody(body []byte, obj any) error {
    return decodeIon(bytes.NewReader(body), obj)
}

func decodeIon(r io.Reader, obj any) error {
    reader := ion.NewReader(r)
    decoder := ion.NewDecoder(reader)

    if err := decoder.DecodeTo(obj); err != nil {
        return err
    }

    if binding.Validator == nil {
        return nil
    }
    return binding.Validator.ValidateStruct(obj)
}

render

type IonRender struct {
    Data any
}

func Ion(data any) IonRender {
    return IonRender{Data: data}
}

func (ir IonRender) Render(w http.ResponseWriter) error {
    writeIonContentType(w)

    bytes, err := ion.MarshalBinary(ir.Data)
    if err != nil {
        return err
    }
    _, err = w.Write(bytes)
    return err
}

func (IonRender) WriteContentType(w http.ResponseWriter) {
    writeIonContentType(w)
}

func writeIonContentType(w http.ResponseWriter) {
    header := w.Header()
    if val := header["Content-Type"]; len(val) == 0 {
        header["Content-Type"] = []string{"application/ion"}
    }
}