Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[binding error] Parsing audio file sent in the request body together with query params #111

Open
norbertstrzelecki opened this issue Mar 6, 2024 · 1 comment

Comments

@norbertstrzelecki
Copy link

While sending an audio file within the request body, I'm receiving the following error:

{
    "error": "binding error: error parsing request body: invalid character 'R' looking for beginning of value"
}

As I understand, the audio file is parsed as a string and not the binary file -- the "R" is from string "RIFF" file header. The question is whether I could bind it as an audio/wav type as described below?

The request looks like this:

curl --location 'http://0.0.0.0:8080/test?userId=some-id' \
--header 'accept: application/json' \
--header 'Content-Type: audio/wav' \
--data '@/audio_file.wav'

So I send userId as a param and binary audio file in the body as audio/wav content type.

Code-wise, the handler functions look like this:

// Bind returns router parameters
func (h *Handler) Bind(router *fizz.Fizz) {
	router.POST("/test", []fizz.OperationOption{
		fizz.ID("test"),
	}, tonic.Handler(h.handle, http.StatusOK))
}

func (h *Handler) handle(c *gin.Context, _ *Evaluation) (*Response, error) {
	var request Request
	err := c.ShouldBindQuery(&request)
        /.../
	audio, err := io.ReadAll(c.Request.Body)
       /.../

	input := Evaluation{
		UserID:          request.UserID,
		Audio:            audio,
	}
	eval := h.Evaluate(c, input)
        /.../

	return &Response{Eval: eval}, nil

and the structs:

type Evaluation struct {
	UserID    string `query:"userId" validate:"required" description:"Id of user"`
	Audio     []byte `format:"binary"`
}

type Request struct {
	UserID    string    `query:"userId" validate:"required" description:"Id of user"`
}

type Response struct {
	Eval    string    `json:"eval"`
}

BTW While generating OpenAPI YAML, the part I'm interested in looks like this:

      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TestInput'

components:
  schemas:
    TestInput:
      type: object
      properties:
        Audio:
          type: string
          format: binary

and I need this:

      requestBody:
        content:
          audio/wav:
            schema:
              type: string
              format: binary
@jonas0616
Copy link

I've run into that too. Here's a workaround until fizz and tonic get updated.
So, the binding error happens because tonic tries to use JSON unmarshal when it sees a handler with a request object. To get around it, you gotta set up a custom binding hook:

type Evaluation struct {
	UserID    string `query:"userId" validate:"required" description:"Id of user"`
}

// implement Binder for your request struct
func (e *Evaluation) ShouldBind() bool {
    return false
}

type Binder interface {
    ShouldBind() bool
}

func CustomBindHook(c *gin.Context, in any) error {
    if binder, ok := in.(Binder); ok {
        if !binder.ShouldBind() {
            return nil
        }
    }
    return tonic.DefaultBindingHook(c, in)
}

// add following code somewhere when setup route
tonic.SetBindHook(CustomBindHook)

Next, since you cannot set custom content types in fizz right now, you'll need to tweak the OpenAPI schema object directly. Because it's a bit of a hassle, I won't be writing the code here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants