Detecting failed struct field - govalidator - go

I'm experimenting with govalidator - https://github.com/asaskevich/govalidator
I would like to know whether it's possible to detect which field in a struct has failed a validation check so that I can return an appropriate error message. So for example:
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"required"`
}
result, err := govalidator.ValidateStruct(post)
if err != nil {
//if title is missing then show error 1
//if message is missing then show error 2
}

This seems to be similar to issue/67:
At this moment it gives error like this:
Title: My123 does not validate as alpha;
AuthorIP: 123 does not validate as ipv4;
I create function ErroByField(e error, field string) that will return error for specified field of struct or empty string otherwise, I hope that it will be helpful.
For example:
type Post struct {
Title string `valid:"alpha,required"`
Message string `valid:"ascii"`
AuthorIP string `valid:"ipv4"`
}
post := &Post{"My123", "duck13126", "123"}
result, err := govalidator.ValidateStruct(post)
titleError := govalidator.ErrorByField(err, "Title")
if titleError != "" {
println(titleError) // -> My123 does not validate as alpha
}

Related

Why do I get "Composite Literal Uses Unkeyed" error?

I'm relatively new to Go and am working on building out a request decoder. The request comes in JSON format and we decode that to a map[string]interface{}. We then pass that object data in to be decoded to our own ProcessRequest struct. As I said I'm new so I am reusing some logic in similar parts of the code wrote by previous developers. Essentially we are checking the map for the necessary pieces and then setting and returning those. Can someone explain to me why I am getting the titled error? Would I have to set the items all the way down to base structs that no longer have any nested? Is there a better way to accomplish what I want? Here is the code and the related structs. It is highlighting the error on the return of the model.ProcessRequest. TYIA
type ProcessRequest struct {
RequestID string
Message *message.Message
Rule *Rule
Options *ProcessOptions
//TODO: Context EvaluatorContext
//TODO: Links
}
type Message struct {
ID int
Key string
Source string
Headers *HeaderSet
Categories *CategorySet
Properties *PropertySet
Streams *StreamSet
}
type RuleAction struct {
Name string
Expression map[string]interface{}
}
type RuleLink struct {
LinkID int
Condition map[string]interface{}
TargetRuleID int
}
type Rule struct {
Name string
Code string
Actions []RuleAction
Links []RuleLink
}
type object = map[string]interface{}
func DecodeProcessRequest(dataObject map[string]interface{}) (*model.ProcessRequest, error) {
var (
requestID string
message *message.Message
rule *model.Rule
options *model.ProcessOptions
err error
)
if reqIDSrc, ok := dataObject["requestId"]; ok {
if requestID, err = converter.ToString(reqIDSrc); err != nil {
return nil, errors.Wrapf(err, "Error reading property 'requestID'")
}
if requestID == "" {
return nil, errors.Errorf("Property 'requestID' is an empty string")
}
} else {
return nil, errors.Errorf("Missing required property 'requestID'")
}
if messageSrc, ok := dataObject["message"]; ok {
messageData, ok := messageSrc.(object)
if !ok {
return nil, errors.Errorf("Error reading property 'message': Value is not an object")
}
if message, err = DecodeMessage(messageData); err != nil {
return nil, errors.Wrapf(err, "Error reading property 'message'")
}
} else {
return nil, errors.Errorf("Missing required property 'message'")
}
if ruleSrc, ok := dataObject["rule"]; ok {
ruleObj, ok := ruleSrc.(object)
if !ok {
return nil, errors.Errorf("Error reading property 'rule': Value is not an object")
}
if rule, err = DecodeRule(ruleObj); err != nil {
return nil, errors.Wrapf(err, "Error reading 'rule' during decoding")
}
} else {
return nil, errors.Errorf("Missing required property 'requestID'")
}
// Parse plain object to a Message struct
return &model.ProcessRequest{
requestID,
message,
rule,
options,
}, nil
}
super said in this comment:
In general, the warning says that you should prefer to use the syntax ProcessRequest{ RequestID: requestID, ... }. Naming the keys instead of unkeyed values.
That worked for me. Also the explanation by kostix in this comment really helped.
Basically the idea is that if you use "unkeyed" way of defining struct literals, the meaning of your definitions depends on the way the fields of the underlying type are layed out. Now consider that your type has three fields of type string in a certain order. Now a couple of iterations down the road some programmer moves the second field to the 1st position—your literal will still compile but will end up defining a completely different value at runtime.

Validator v10 get list of error on single field

Is there a way to get the list of errors for each validation that fails?
Currently I'm just receiving one error, and comes from the first validation that fails.
I'm using golang and Validator v10
type User struct {
ID string `param:"id" json:"id" validate:"required"`
Name string `json:"name" validate:"required,max=100"`
Permissions []string `json:"permissions" validate:"permission_list,distinct_list"`
}
So if the field Permissions fails on both I can get as result:
{
"validationErrors": {
"permissions": [
"permissions must be a list of valid permissions",
"permissions must be uniques",
]
}
}
You'll need to inspect the error returned.
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
type User struct {
FirstName string `validate:"required"`
LastName string `validate:"required"`
}
func main() {
testUser := User{}
v := validator.New()
if err := v.Struct(testUser); err != nil {
validationErrors := err.(validator.ValidationErrors)
for _, validationError := range validationErrors {
fmt.Println(validationError.Error())
}
}
}
Example:
https://play.golang.com/p/HFdHrPKiYwF
Check the code at https://github.com/go-playground/validator/blob/master/errors.go for methods available on ValidationErrors and FieldError.
If what you need is not just only check one validate condition then return it, but need to check every validate that failed instead, you need to do a little enhancement on the package, like my case. I need to remove return statement from validator.go source on line 480

Define a custom type

I want to define a new type in GORM like this:
type Device struct{
gorm.Model
Name string
Status Status
}
Where the values of Status can only be either one of ok, broken, or missing. Obviously, I can use the type string for this, but then I would allow every string possible. How can I restrict the options to the ones I want?
the simplest thing would be something like this:
const (
statusOk = "ok"
statusBroken = "broken"
statusMissing = "missing"
)
func isValidStatus(s string) error {
switch s {
case statusOk, statusBroken, statusMissing:
return nil
}
return errors.New("Invalid status")
}
var d Device
// your code
// your code
// your code
if err := isValidStatus(d.Status); err != nil {
// handle error
}
// valid status

Chaincode GetState returns an empty response

func (t *ballot) initBallot(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
// ==== Input sanitation ====
fmt.Println("- start init ballot")
if len(args[0]) == 0 {
return shim.Error("1st argument must be a non-empty string")
}
if len(args[1]) == 0 {
return shim.Error("2nd argument must be a non-empty string")
}
personFirstName := args[0]
personLastName := args[1]
hash := sha256.New()
hash.Write([]byte(personFirstName + personLastName)) // ballotID is created based on the person's name
ballotID := hex.EncodeToString(hash.Sum(nil))
voteInit := "VOTE INIT"
// ==== Create ballot object and marshal to JSON ====
Ballot := ballot{personFirstName, personLastName, ballotID, voteInit}
ballotJSONByte, err := json.Marshal(Ballot)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(string(ballotID), ballotJSONByte)
//FIXME:0-------------------------------------------------
ballotAsByte, err := stub.GetState(string(ballotID))
if err != nil {
return shim.Error(err.Error())
}
BBBallot := ballot{}
//umarshal the data to a new ballot struct
json.Unmarshal(ballotAsByte, &BBBallot)
//
fmt.Println(BBBallot)
fmt.Println(BBBallot.personFirstName)
return shim.Success([]byte(ballotID))
}
Above is the code and this is the test script i am running it against
func Test_Invoke_initBallot(t *testing.T) {
scc := new(ballot)
stub := shim.NewMockStub("voting", scc)
res := stub.MockInvoke("1", [][]byte{[]byte("initBallot"), []byte("John"), []byte("C")})
if res.Status != shim.OK {
t.Log("bad status received, expected: 200; received:" + strconv.FormatInt(int64(res.Status), 10))
t.Log("response: " + string(res.Message))
t.FailNow()
}
if res.Payload == nil {
t.Log("initBallot failed to create a ballot")
t.FailNow()
}
}
I am trying to read from the ledger after putting the transaction in. However, I have been getting empty responses from both of the Println statements.
// PutState puts the specified `key` and `value` into the transaction's
// writeset as a data-write proposal. PutState doesn't effect the ledger
// until the transaction is validated and successfully committed.
// Simple keys must not be an empty string and must not start with null
// character (0x00), in order to avoid range query collisions with
// composite keys, which internally get prefixed with 0x00 as composite
// key namespace.
PutState(key string, value []byte) error
It does say on the documentation that putState does not commit transactions to the ledger until its validated, but I am just trying to test my chaincode using the MockStub without setting up the fabric network. What is the fix to this problem?
P.S the problem has been solved, here is the right way to set up a struct
type ballot struct {
PersonFirstName string
PersonLastName string
BallotID string
VoteInit string
}
You haven't provided the code for the ballot struct yet. But from what you provided, I have a guess what might be going on. I think you probably haven't exported the fields and your struct looks like this:
type ballot struct {
personFirstName string
personLastName string
ballotID string
voteInit string
}
But when you tried to convert this object to JSON using json.Marshal(Ballot), none of the fields are added to the JSON object because they were not exported. All that you have to do in this case is exporting the necessary fields (using Uppercase letter at the beginning of field names). Your updated struct should look something like the following:
type ballot struct {
PersonFirstName string
PersonLastName string
BallotID string
VoteInit string
}
This is a very common mistake many newcomers make. Wish you all the best in your journey forward!!!
P.S. Please edit your question and add the code of you ballot struct here even if this solution solves your problem as that might help others in the future. Also, please add proper indentation to the code and add the last } symbol in the code block.

combining GO GIN-GONIC GORM and VALIDATOR.V2

I am quite new to Go and I would like to startup by setting a GIN-GONIC API. I found this tutorial and I am very happy with that skeleton. But now I am stuck with the validating process which I added: "gopkg.in/validator.v2" and
type Todo struct {
gorm.Model
Title string `json:"title"`
Completed int `json:"completed"`
}
became
type Todo struct {
gorm.Model
Title string `json:"title" **validate:"size:2"**`
Completed int `json:"completed"`
}
and then in the CreateTodo function which I added :
if errs := validator.Validate(todo); errs!=nil {
c.JSON(500, gin.H{"Error": errs.Error()})
}
but then a POST call send :
"Error": "Type: unknown tag"
after some research I found that :
Using a non-existing validation func in a field tag will always return false and with error validate.ErrUnknownTag.
so the **validate:"size:2"** must be wrong ...
I don't get how to set the validation and also how to display the correct error within the "catch":
c.JSON(500, gin.H{"Error": errs.Error()})
Looks like you haven't defined size validation function. Also you can do it.
Custom validation functions:
func size(v interface{}, param int) error {
st := reflect.ValueOf(v)
if st.Kind() != reflect.String {
return validate.ErrUnsupported
}
if utf8.RuneCountInString(st.String()) != param {
return errors.New("Wrong size")
}
return nil
}
validate.SetValidationFunc("size", size)

Resources