4

I try to validate if enum is valid in Golang using Gin framework.

I came across this solution:

But disadvantage of this approach is hardcoded value which we have to change manually every time enum value has changed.

Is there any way to get enum by its name as string without creating and register the map with types?

Desired result:

package main

import "github.com/go-playground/validator"

type Status int

const (
    Single Status = iota
    Married
    Other
)

type User struct {
    Status Status `json:"status" binding:"Enum=Status"`
}

func Enum(fl validator.FieldLevel) bool {
    enumType := fl.Param() // Status
    // get `Status` by `enumType` and validate it...
    return true
}

func main() {}
mikolaj semeniuk
  • 2,030
  • 15
  • 31

2 Answers2

1

One of the approach to this solution could be:

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/validator/v10"
)

type Enum interface {
    IsValid() bool
}

type Status int

const (
    Single Status = iota + 1 // add + 1 otherwise validation won't work for 0
    Married
    Other
)

func (s Status) IsValid() bool {
    switch s {
    case Single, Married, Other:
        return true
    }

    return false
}

type Input struct {
    RelationshipStatus Status `json:"relationship_status" binding:"required,enum"`
}

func UpdateRelationshipStatus(context *gin.Context) {
    input := Input{}

    err := context.ShouldBindJSON(&input)
    if err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"message": "enum is not valid"})
        return
    }

    context.JSON(http.StatusOK, gin.H{"message": "correct enum"})
}

func ValidateEnum(fl validator.FieldLevel) bool {
    value := fl.Field().Interface().(Enum)
    return value.IsValid()
}

func main() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("enum", ValidateEnum)
    }

    router := gin.Default()

    router.POST("", UpdateRelationshipStatus)

    router.Run(":3000")
}

Output:

curl \
  --request POST \
  --data '{"relationship_status": 0}' \
  http://localhost:3000/
# {"message":"enum is not valid"}

curl \
  --request POST \
  --data '{"relationship_status": 1}' \
  http://localhost:3000/
# {"message":"correct enum"}

curl \
  --request POST \
  --data '{"relationship_status": 2}' \
  http://localhost:3000/
# {"message":"correct enum"}

curl \
  --request POST \
  --data '{"relationship_status": 3}' \
  http://localhost:3000/
# {"message":"correct enum"}

curl \
  --request POST \
  --data '{"relationship_status": 4}' \
  http://localhost:3000/
# {"message":"enum is not valid"}
mikolaj semeniuk
  • 2,030
  • 15
  • 31
  • _But disadvantage of this approach is hardcoded value which we have to change manually every time enum value has changed._ isn't this exactly what you do *not* want? – Arkadiusz Drabczyk Feb 26 '22 at 19:00
  • So far I didn't find a better way that's way I posted this solution, but if you post finer answer I would be glad to accept it. – mikolaj semeniuk Feb 26 '22 at 19:07
  • 1
    But first you said: _Is there any way to get enum by its name as string_ - how is this requirement covered in your answer? – Arkadiusz Drabczyk Feb 26 '22 at 19:15
1

Agree with mikolaj's answer. But to avoid the disadvantage which comes with this approach, you can try to leverage something like https://github.com/nishanths/exhaustive which checks and errors in CICD systems if you forget to handle the new const value in switch cases.

Viggi
  • 215
  • 3
  • 7