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

Working with BindJSON and error return #815

Closed
depado opened this issue Feb 21, 2017 · 18 comments
Closed

Working with BindJSON and error return #815

depado opened this issue Feb 21, 2017 · 18 comments

Comments

@depado
Copy link

depado commented Feb 21, 2017

Hello,

First of all, thanks for your great project, I'm using it a on daily basis and it's just awesome.

TL;DR

I have no clue on how to properly handle response format when validation fails on BindJSON and I want to send back the errors as JSON.

Context

I'm currently having trouble working with context.BindJSON. I have two fields that are required when receiving JSON, let's call them first and second

type params struct {
	First  string `json:"first" binding:"required"`
	Second string `json:"second" binding:"required"`
}

Now when I receive the request, I handle it like this :

func postFirstSecond(c *gin.Context) {
	var err error
	c.Header("Content-Type", "application/json; charset=utf-8")

	p := params{}
	if err = c.BindJSON(&p); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":  "json decoding : " + err.Error(),
			"status": http.StatusBadRequest,
		})
		return
	}
}

So first of all, I don't understand why c.JSON() doesn't return the appropriate content-type, I found that when BindJSON fails, it writes the header to be plain text. Anyway my endpoint always returns JSON so I just set the content-type on top of the handler, problem solved (I guess).

When one field is missing, this endpoint returns something like that :

{
    "error": "json decoding : Key: 'params.First' Error:Field validation for 'First' failed on the 'required' tag",
    "status": 400
}

That's fine. I mean it's not really what I expected but it works.

Problem

When both fields are missing, this is what I get back :

{
    "error": "json decoding : Key: 'params.First' Error:Field validation for 'First' failed on the 'required' tag\nKey: 'params.Second' Error:Field validation for 'Second' failed on the 'required' tag",
    "status": 400
}

Question

Is there a simple way to handle those errors ? I'd like to get something more user-friendly, or at least a separate field for my errors. Something like :

{
    "errors": [
        "Key: 'params.First' Error:Field validation for 'First' failed on the 'required' tag",
        "Key: 'params.Second' Error:Field validation for 'Second' failed on the 'required' tag"
     ],
    "status": 400
}

I'm aware that checking manually the fields should allow me to do that, but the fact is : I feel like the documentation and examples are pretty vague about error handling.

Could someone help me with that ? How do you handle errors generated by Bind or BindJSON ?

@deankarn
Copy link

@depado the error returned is actually of type ValidationErrors and you can cast it and then for or range over each FieldError in the map to get separate fields.

Also for those that may be using v9 of validator the definition is no longer a map https://github.com/go-playground/validator/blob/v9/errors.go#L37

@depado
Copy link
Author

depado commented Feb 24, 2017

Thanks @joeybloggs I'll give that a try 👍

@toefel18
Copy link

I also experience the error that the 'Content-Type' is text/plain instead of 'application/json' when I have a binding error and return a error struct using c.JSON()

@deankarn
Copy link

Yes you are correct @toefel18 not all errors that can be returned are ValidationErrors some are related to, as you pointed out, Content-Type and also JSON decoding errors.

so one would have to ensure the cast succeeds:

err:= Bind...
ve, ok := err.(validator.ValidationErrors)
if !ok {
    // non validation error... handle appropriately.
}

@toefel18
Copy link

@joeybloggs that is indeed true, but the problem I am referring to is different

if err := c.Bind(&login); err != nil {
     c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}

Output is JSON, response Content-Type is "text/plain". I thought that is what @depado meant. Should I file another bug request?

@deankarn
Copy link

I see @toefel18 although I am not a part of the gin team, I'd say yes it does seem like a separate issue.

P.S. may want to check there isn't one open already, for some reason it sounds familiar :)

@toefel18
Copy link

@joeybloggs, okay I will look into it later today 👍

@depado
Copy link
Author

depado commented Feb 28, 2017

The Content-Type thing is a known issue.

@appleboy
Copy link
Member

appleboy commented Feb 28, 2017

@toefel18 @depado Try c.AbortWithStatusJSON function in gin develop branch. Please ref: #800

@toefel18
Copy link

toefel18 commented Mar 1, 2017

Ah great, thanks @depado @appleboy!

@appleboy
Copy link
Member

@depado Does c.AbortWithStatusJSON function resolve your problem abount Content-Type?

@depado
Copy link
Author

depado commented Mar 27, 2017

Looks like it does but the main point of this issue was about the parsing of the returned error of the c.Bind method.

@nazwa
Copy link

nazwa commented Mar 27, 2017

@depado you can use a middleware to check for any binding errors. There was a long thread about it somewhere here a while ago.

@AlexeyAtIgloo
Copy link

I've encountered similar issue, I have global after middleware that creates json response with errors when errors are present on the context and I also set header to json in that middleware, but because framework writes headers when handling bind errors, its impossible to overwrite them later... My issue #840, I also have a workaround bind function i'm using for now, see my issue for it.

@mymtw
Copy link

mymtw commented Aug 3, 2017

I have the same issue

        var ucr messages.UsersCreateRequest
	if e := c.BindJSON(&ucr); e != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"errors": "someError/nHere/nAndYeatOneError",
		})
		return
	}

then sometimes I'm getting text/plain, but should text/json, sometimes I'm getting text/json
in response.headers.get('content-type')

@easonlin404
Copy link
Contributor

easonlin404 commented Aug 4, 2017

@mymtw if you want to get Content-Type application/json header when validating failed , you can use ShouldBindWith instead of Bind function that will not call AbortWithError.

ref: #1041 (comment)

@mymtw
Copy link

mymtw commented Aug 4, 2017

@easonlin404 thx, works

@appleboy
Copy link
Member

appleboy commented Aug 4, 2017

Everybody can see the PR. #1047

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

9 participants