-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
validate.Struct() always return error #134
Comments
Hey @alioygur that's an interesting one, I do have a few questions:
This is just a guess before I can get how your checking the nil, but if your using v6 and you are printing the error and it outputs map[] this is a let's call it goism for lack of a better term. fmt.Println and log.Println don't always print the value but just call the String() function of the object, by default, and that function returns a string. V6 returns a type ValidationErrors which is really a map[string]*FieldError and a map in go is a by-reference type, meaning it's always passed by reference. Because of this a maps default value is nil, but printing it out it's Internal String() function will print "map[]" which is it's type and not value. Assuming this is all true if you do fmt.Println(err == nil) it will print "true", it is nil, just doesn't print that way by default in go unless other options with Printf are used, see fmt package documentation for those. Either way let me know |
i am using v6, there is sample.
|
I will take a look at this asap, probably a little later tonight and get back to you. |
P.s. doe's it work without name error parameters? |
This is a little tricky to explain, but I'll try my best: Understanding By-Reference for mapok so like I have mentioned above map is one of go's by reference data types, so what that means is that the map is actually a pointer to location in memory. In this case the pointer can point to a valid ValidationErrors object or nil; but either way the pointer is always valid even if the value it points to isn't in go you can access the values of reference types just like you would as if they were not ( except in special cases but we're not getting into that ) so that's why this works: // declaring variables in long form for clarity
var err ValidationErrors = validate.Struct(user)
// when accessing err it will actually access the value in memory which may be nil or valid
if err != nil {
// ...
} SituationIn validateStruct() your assigning err = validate.Struct(user) but the return value of validate.Struct() is of type ValidationErrors, you can do this because ValidationErrors implements the error interface; however it does not work quite how one would expect. ExpectedThe value of err gets set to the value of the ValidationErrors, which in your example is nil. What Actually HappensThe value of err gets set to the pointer of ValidationErrors which points to it's value Why not pass back error from validate.Struct() instead of ValidationErrorsIt's true that this would make your failing example work as expected. Doing it this way was a design decision so that some people may avoid unnecessary type assertions. Take the following example into consideration, many people, including myself, handle their errors right away, without passing the error around as error. for example // err will be ValidationErrors
err := validate.Struct(user)
if err != nil {
// range over err, create error messages, based off of FieldError
// information and return those errors, or a map[string]string
}
return formatted errors but if I got type error returned err = validate.Struct(user)
if err != nil {
// now to range over my ValidationErrors I would need to assert
// that the err object is in fact ValidationErrors
valErrs := err.(validator.ValidationErrors)
// then you can range over valErrs or whatever, however you see
// that we had to assign another variable and do a type assertion,
// although this is pretty light weight it still has some overhead
} so those that pass the ValidationErrors as error and want access to the FieldError data will need to do type assertions, but not everyone has to the way it works now. Recommended Approach if you still wish to pass by errorfunc validateStruct() error {
user := &User{
FirstName: "Badger",
LastName: "Smith",
}
err := validate.Struct(user)
if err != nil {
return err
}
return nil
} @alioygur sorry for being so long winded about this, but because you said you were new to go I just wanted the answer to be as thorough as possible. I hope I made sense, do you have any questions? Final thoughts - also because you said you were new to goFeel completely free to totally disregard this recommendation, it's just a suggestion, as I've played around with named return params myself and stopped completely. I would recommend not using named return params they are a super micro micro optimization that tends, IMHO, to really hurt code maintainability; they seem like a good idea, but as your functions size grows and you have to constantly scroll back to see what you named the variable, and tracing back where a value was last set, or if a value was set really slow you down. On the flip side returning where and when you need to is easier to read, even for those that did not write the code, and is much clearer about what is happening. |
@joeybloggs so so thank you for your response. i will not use the named error. |
No problem @alioygur I'll leave this issue open for a few days before I close it. If you have any more questions, issues or any recommendations don't hesitate to ask! |
@joeybloggs , can you review this code for me ? |
Sure |
It seems like you have a pretty good grip on go, code looks very good. And FINALLY! someone using the "dive" tag I worked so hard to have that work like it does 😄 I would recommend a few things, but they are minor and some just pure preference. 1. DeletePost possible issue, see comment in code// DeletePost delete post handler
func DeletePost(c *Context) error {
post := new(Post)
err := DB.Find(post, c.Param(":id")).Error
if err != nil {
return err
}
// does DB.Find return an error when post is not found?
// if not the next line will probably error out
// secondly is not finding a post for deletion an error, or is it ok?
err = DB.Delete(post).Error
if err != nil {
return err
}
return c.NoContent(204)
} 2. Functions that don't return a new parameterthis ones totally a preference, but when a function doesn't return a new variable, save a reusable one like error, or I don't need the new variable below the current line, I tend to write it like this var err error
if err = BindAndValidate(c.Request, req); err != nil
return err
} so your CreatePost could be rewritten in fewer lines, but just as readable // CreatePost create post handler
func CreatePost(c *Context) error {
var err error
req := new(NewPostReq)
if err = BindAndValidate(c.Request, req); err != nil {
return err
}
post := new(Post)
if err = CopyTo(req, post); err != nil {
return err
}
post.UserID = 1
if err = DB.Create(post).Error; err != nil {
return err
}
pr := new(PostResource)
if err = CopyTo(post, pr); err != nil {
return err
}
return c.JSON(201, pr)
} 3. dynamic structsThis one may be because of how JSON is processed, so if so totally ignore this. I usually take the time to create the sub structs that could be dynamic, which allows for reusability later down the line; but there are caveats, see below: // so instead of
type NewPostReq struct {
Title string `validate:"required"`
Description string `validate:"required"`
Body string `validate:"required"`
Tags []struct {
Tag string `validate:"required"`
} `validate:"min=2,dive"`
}
// I would do
type Tag struct {
Name string `validate:"required"`
}
type NewPostReq struct {
Title string `validate:"required"`
Description string `validate:"required"`
Body string `validate:"required"`
Tags []*Tag `validate:"min=2,dive"`
}
// NOTE: you will notice that I used []*Tag, note the *
// although there is debate on this I tend to pass almost
// all struct by reference internally within my code.
// but there are times to lock it down, just take a look
// at Config within my validator, it is passed by value
// so that it gets copied and you can't manipulate it after
initialization. the caveats are
anyways @alioygur I thinks that's it, and again, code looks great, hopefully you can star my repo if this package/library works out for you. P.S. if the code your working on becomes a project, and it's ok with you I would love to start listing some projects on my README that use my library. and be sure to check out some of my other projects here https://github.com/bluesuncorp I'm going to be adding more as time goes on. |
Tank you so much dear. I am not on computer now. I will reply to you
|
@joeybloggs i agree with your all caveats. i will refactor my code by your caveats. thank you. |
No problems @alioygur, going to close this issue now and if you have anymore questions, just shoot me an email. |
hi, i am new in golang, maybe that problem about me.
when i use validator.Struct() in a function that return named error, the err var always not nil
but when i use it like this, it works
so what the problem ?
The text was updated successfully, but these errors were encountered: