I have a following type defined using iota in golang.
type StatusType int
const (
PENDING StatusType = iota
APPROVED
REJECTED
)
I want to restrict the value passed in in REST-API to the StatusType. Such that the value should not exceed 0,1,2.
Simply don't export StatusType
(assuming you define it in package 'status
').
This follow "What is an idiomatic way of representing enums in Go?":
type statusType int
const (
PENDING statusType = iota
APPROVED
REJECTED
)
type StatusTyper interface {
StatusType() statusType
}
func(st statusType) StatusType() statusType {
return st
}
Any external package would then refer to StatusType
-like variable as status.PENDING
, status.APPROVED
or status.REJECTED
.
(the only three statusType
which implement the StatusTyper
interface. Caveat applies.)
Assuming you wish for invalid JSON payloads to fail, implement the Unmarshaler interface: https://play.golang.org/p/zuchzQ0vmo
I do this way:
first create a package named "StatusType" (inside a folder named StatusType):
filename: $GOPATH/enum/StatusType/StatusType.go
package StatusType
type Int int
const (
Pending Int = iota
Approved
Rejected
end
)
func IsValid(value int) bool {
return value < int(end)
}
and use like this ($GOPATH/enum/main.go):
package main
import (
"enum/StatusType"
"fmt"
)
func Test(enum StatusType.Int) {
fmt.Println(enum) //1
}
func main() {
Test(StatusType.Approved)
fmt.Println(StatusType.IsValid(1)) //true
fmt.Println(StatusType.IsValid(10)) //false
}
The StatusType package just exports what you need so there is no need to check against iota const range.
Just in case you want to check, use: StatusType.IsValid()
And nice thing about StatusType package is:
When you want function parameter of StatusType type use StatusType.Int and it reveals that it is enumeration of type int.
Like:
Test(StatusType.Approved)
I am also grappled with this issue, since I really want to find some easy way to check if a value passed in is within the right range. The trick I came up with is as following:
type StatusType int
const (
PENDING StatusType = iota
APPROVED
REJECTED
)
func (st StatusType) String() string {
switch st {
case PENDING:
return "STATUS:PENDING"
case APPROVED:
return "STATUS:APPROVED"
case REJECTED:
return "STATUS:REJECTED"
defaut:
return "INVALID"
}
}
func (st StatusType) IsValid() bool {
return st.String() != "INVALID"
}
Because I often need to translate the enum value to some meaningful representation, I tend to add a String()
to an enum type. The cases cover all legal values, while default path captures the situation where the value is invalid.
Simply put, to check if some value is valid, compare its string representation to default case.
One benefit to my solution is that if another enum value is added in the future, we only need to add a case in String()
method. The IsValid()
method still works.
Another benefit is that in the case when the range of values aren't continuous, my solution also works. Such as the following definition:
const (
PENDING StatusType = 1001
APPROVED = 1003
REJECTED = 1005
)
use go generate with github.com/alvaroloes/enumer
package main
import "fmt"
//go:generate enumer -type=StatusType
type StatusType int
const (
PENDING StatusType = iota
APPROVED
REJECTED
)
func main() {
fmt.Println(StatusType(0).IsAStatusType()) // true
fmt.Println(StatusType(1).IsAStatusType()) // true
fmt.Println(StatusType(2).IsAStatusType()) // true
fmt.Println(StatusType(3).IsAStatusType()) // false
}
the iota is merely a compiler thing. The code is equivalent to:
const PENDING int = 0
const APPROVED int = 1
...
So to design a function CheckValid() to decide if the value is among the given values. You could either use user6169399's method if your const is in a continuous range. Or you could simply define a var map[YOUR_TYPE_HERE]bool to validate.
func (t YOUR_TYPE) CheckValid(){
if _, ok:=map[t];ok return true
else return false
}
Here are another two ways to do it right without map https://play.golang.org/p/eKW_KPshx7b
package main
import (
"errors"
"log"
)
type StatusType int
const (
PENDING StatusType = iota
APPROVED
REJECTED
)
func Validate(val int) (bool, error) {
if v := StatusType(val); v > REJECTED || v < PENDING {
return false, errors.New("invalid StatusType")
}
return true, nil
}
func (t StatusType) Validate() (bool, error) {
if t > REJECTED || t < PENDING {
return false, errors.New("invalid StatusType")
}
return true, nil
}
func main() {
log.Print(Validate(-1))
log.Print(Validate(0))
log.Print(Validate(1))
log.Print(Validate(3))
log.Print(StatusType(-1).Validate())
log.Print(StatusType(1).Validate())
log.Print(StatusType(10).Validate())
}