How to get a Protobuf enum value from its string representation? - go

I can get the string value of a Protobuf enum with this instruction:
str := testPB.Status_ENABLED.String()
How can I perform the inverse operation? (from a string, get the enum element).

The generated code has a map called <EnumName>_value of type map[string]int32. Then you can convert the numerical value to the actual defined type:
num := testPB.Status_value[str]
v := testPB.Status(num)
Consider that if the str value doesn't exist in the map (note that it's case sensitive), the map look-up will return 0. Depending on how you defined your protobuffer, the 0 value might be mapped to a an enum instance that does not have "zero" semantics. This is why it is recommended to map 0 to an "unknown" instance:
enum Status {
UNKNOWN = 0;
ENABLED = 1;
// and so on
}
Which in Go correctly yields a makeshift zero-value if the string representation is effectively unknown:
v := testPB.Status(testPB.Status_value["does_not_exist"])
fmt.Println(v == testPB.Status_UNKNOWN) // true
Go 1.18
With generics, it is possible to write reusable code to construct protobuffer enums from string values:
func Enum[T ~string, PB ~int32](val T, pbmap map[string]int32, dft PB) PB {
v, ok := pbmap[string(val)]
if !ok {
return dft
}
return PB(v)
}
where:
the type parameter T is the string representation, which could also be a type with underlying type string, e.g. type MyEnum string
the argument pbmap is the <EnumName>_value from the generated protobuffer code
the type parameter PB is the protobuffer enum type.
The function above takes a default value of type PB to (obviously) have a fallback in case the string representation is invalid, and also to allow type inference on PB, which otherwise would be used only as a return type.
Usage:
type SomeEnum string
const (
SomeEnumFoo SomeEnum = "FOO"
SomeEnumBar SomeEnum = "BAR"
)
func main() {
foo := SomeEnumFoo
v := Enum(foo, pb.SomeEnum_value, pb.SomeEnum_Foo)
// ^ T ^ map[string]int32 ^ default PB value
// v is type pb.SomeEnum
}

Related

Assigning to type definition using reflection in Go

I have setup a type called Provider that defines an integer:
type Provider int
func (enum *Provider) Scan(raw interface{}) error {
*enum = Provider(int(raw))
}
If I create an object, Foo, with a Provider field, like this:
type Foo struct {
Code Provider
Value string
}
foo := &Foo {
Code: Provider(0),
Value: "derp",
}
var scnr sql.Scanner
scannerType := reflect.TypeOf(&scnr).Elem()
tType := reflect.TypeOf(foo)
tField := tType.Field(0)
fmt.Printf("Field %s, of type %s, kind %s\n",
tField.Name, tField.Type, tField.Type.Kind())
When I run this code, I get that the field is of type Provider and its Kind is int. However, I cannot assign an int to a Provider using reflection because this will fail:
getInteger() interface{} {
return 1
}
fValue := reflect.ValueOf(foo).Elem()
vField := fValue.Field(0)
vField.Set(reflect.ValueOf(getInteger())) // panic: reflect.Set: value of type int is not assignable to type Provider
The normal solution to this would be to do reflect.ValueOf(Provider(getInteger().(int))), however, this won't work because vField is set inside of a loop that iterates over a structure's fields and therefore will have a different type. Essentially, I would like a way to detect that vField is a definition of int (ie. Provider) rather than an int, itself; so I could use reflection to cast the integer value to Provider when I set it.
Is there a way I can make this work?
The kind and type are not identical terms. Kind of both int and Provider is int because Provider has int as its underlying type. But the type Provider is not identical to int. What you have is a type definition, but not a type alias! Those are 2 different things. See Confused about behavior of type aliases for details.
The type of Foo.Code field is Provider, you can only assign a value of type Provider to it:
vField.Set(reflect.ValueOf(Provider(1)))
This works.
Note that reflection or not, you can't assign int to Foo.Code:
var i int = 3
var foo Foo
foo.Code = i // Error!
The above has compile time error:
error: cannot use i (variable of type int) as type Provider in assignment
What may be confusing is that using a constant works:
var foo Foo
foo.Code = 3 // Works!
This is because 3 is an untyped constant representable by a value of type Provider, so the constant will be converted to Provider.
I ended up using the Convert function in conjunction with the Assignable function to do my check:
// Get value to assign and its type
vValue := reflect.ValueOf(getInteger())
tValue := vValue.Type()
// Check if the field can be assigned the value; if it can then do so
// Otherwise, check if a conversion can be assigned
vType := vField.Type()
if tValue.AssignableTo(vType) {
vField.Set(vValue)
} else if vValue.CanConvert(vType) {
vField.Set(vValue.Convert(vType))
}
This fixed my issue.

Casting pointer value expression

I'm trying to figure out what this expression does:
(*levelValue)(&level)
I don't understand what is happening, it seems like it dereferencing levelValue first, but not sure why as the type of levelValue is int32
Some context below
import "flag"
type Level int32
type levelValue Level
// LevelFlag defines a Level flag with specified name, default value and
// usage string. The return value is the address of a Level value that stores
// the value of the flag.
func LevelFlag(name string, defaultLevel Level, usage string) *Level {
level := defaultLevel
flag.Var((*levelValue)(&level), name, usage)
return &level
}
func (l *levelValue) Set(s string) error {
return (*Level)(l).UnmarshalText([]byte(s))
}
func (l *levelValue) String() string {
return (*Level)(l).String()
}
Reference
This is a type conversion:
When you define a type like that
type A B
You can convert a variable of type B to type A like that:
b := B
a := A(b)
In your case, the type A is (*levelValue) (The parenthesis are needed to specify that the type is a pointer to a levelValue. And the variable b is &level (A pointer that points to the variable level)

What is the default value of a map of struct

What is the default value of struct in a map? How to check the map value is initialized?
type someStruct struct {
field1 int
field2 string
}
var mapping map[int]someStruct
func main() {
mapping := make(map[int]someStruct)
}
func check(key int) {
if mapping[key] == ? {}
}
Should I check against nil or someStruct{}?
Default value of a struct is zero value for each field which is different on basis of its type.
When storage is allocated for a variable, either through a
declaration or a call of new, or when a new value is created, either
through a composite literal or a call of make, and no explicit
initialization is provided, the variable or value is given a default
value. Each element of such a variable or value is set to the zero
value for its type: false for booleans, 0 for numeric types, "" for
strings, and nil for pointers, functions, interfaces, slices,
channels, and maps. This initialization is done recursively, so for
instance each element of an array of structs will have its fields
zeroed if no value is specified.
type T struct { i int; f float64; next *T }
t := new(T)
the following holds:
t.i == 0
t.f == 0.0
t.next == nil
But for checking the value of a map based on the key if it exists you can use it as:
i, ok := m["route"]
In this statement, the first value (i) is assigned the value stored under the key "route". If that key doesn't exist, i is the value type's zero value (0). The second value (ok) is a bool that is true if the key exists in the map, and false if not.
For your question
Should I check against nil or someStruct{} ?
To check for initialized empty struct you can check for someStruct{} as:
package main
import (
"fmt"
)
type someStruct struct {
field1 int
field2 string
}
var mapping map[int]someStruct
func main() {
var some someStruct
fmt.Println(some == (someStruct{}))
//mapping := make(map[int]someStruct)
}
Go playground

Does type assertion change the value in go?

Go newbie here.
I have a map where the key arguments should be []string.
However, if I try to use the value directly arguments := m["arguments"] it doesn't seem to be the right type. When used later to append to another slice with arguments... I get Cannot use 'arguments' (type interface{}) as type []string.
I fixed this by chaning the assignment to a type check arguments, _ := m["arguments"].([]string). That works, but I'm not sure why. Is type assertion doing conversion as well?
The full example is below:
import (
"github.com/fatih/structs"
"strings"
)
var playbookKeyDict = map[string]string{
"Playbook": "",
"Limit" : "--limit",
"ExtraVars" : "--extra-vars",
}
type Playbook struct {
Playbook string `json:"playbook" xml:"playbook" form:"playbook" query:"playbook"`
Limit string `json:"limit" xml:"limit" form:"limit" query:"limit"`
ExtraVars string `json:"extra-vars" xml:"extra-vars" form:"extra-vars" query:"extra-vars"`
Arguments []string `json:"arguments" xml:"arguments" form:"arguments" query:"arguments"`
Args []string
}
func (p *Playbook) formatArgs() {
// is it worth iterating through directly with reflection instead of using structs import?
// https://stackoverflow.com/questions/21246642/iterate-over-string-fields-in-struct
m := structs.Map(p)
// direct assignment has the wrong type?
// arguments := m["arguments"]
arguments, _ := m["arguments"].([]string)
delete(m, "arguments")
for k, v := range m {
// Ignore non-strings and empty strings
if val, ok := v.(string); ok && val != "" {
key := playbookKeyDict[k]
if key == "" {
p.Args = append(p.Args, val)
} else {
p.Args = append(p.Args, playbookKeyDict[k], val)
}
}
}
p.Args = append(p.Args, arguments...)
}
Type assertion is used to get a value wrapped around using interface.
m := structs.Map(p)
Map(v interface{}){}
Map function is actually taking interface as its argument in the case stated. It is wrapping the type which is []string and its underlying value which is slice. The type can be checked using Relection reflect.TypeOf().
func TypeOf(i interface{}) Type
According to Russ Cox blog on Interfaces
Interface values are represented as a two-word pair giving a pointer
to information about the type stored in the interface and a pointer to
the associated data.
As specified in Golang spec
For an expression x of interface type and a type T, the primary
expression
x.(T)
asserts that x is not nil and that the value stored in x is of type T.
The notation x.(T) is called a type assertion.
For the error part:-
Cannot use 'arguments' (type interface{}) as type []string
We first needs to get the underlying value of type []string from interface using type assertion.

Identify non builtin-types using reflect

I need to differentiate such types as
type A []byte
from a []byte. Using reflect, reflect.TypeOf(A{}).Kind tells me that it is a Slice of byte. How can I differentiate []byte{} from A{}, without having a bounded list of types to check for?
Are there new ways to do it in newer versions of Go?
Some background
First let's clear some things related to types. Quoting from Spec: Types:
A type determines the set of values and operations specific to values of that type. Types may be named or unnamed. Named types are specified by a (possibly qualified) type name; unnamed types are specified using a type literal, which composes a new type from existing types.
So there are (predeclared) named types such as string, int etc, and you may also create new named types using type declarations (which involves the type keyword) such as type MyInt int. And there are unnamed types which are the result of a type literal (applied to / including named or unnamed types) such as []int, struct{i int}, *int etc.
You can get the name of a named type using the Type.Name() method, which "returns an empty string for unnamed types":
var i int = 2
fmt.Printf("%q\n", reflect.TypeOf("abc").Name()) // Named: "string"
fmt.Printf("%q\n", reflect.TypeOf(int(2)).Name()) // Named: "int"
fmt.Printf("%q\n", reflect.TypeOf([]int{}).Name()) // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(struct{ i int }{}).Name()) // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(&struct{ i int }{}).Name()) // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(&i).Name()) // Unnamed: ""
There are types which are predeclared and are ready for you to use them (either as-is, or in type literals):
Named instances of the boolean, numeric, and string types are predeclared. Composite types—array, struct, pointer, function, interface, slice, map, and channel types—may be constructed using type literals.
Predeclared types are:
bool byte complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr
You may use Type.PkgPath() to get a named type's package path, which "if the type was predeclared (string, error) or unnamed (*T, struct{}, []int), the package path will be the empty string":
fmt.Printf("%q\n", reflect.TypeOf("abc").PkgPath()) // Predeclared: ""
fmt.Printf("%q\n", reflect.TypeOf(A{}).PkgPath()) // Named: "main"
fmt.Printf("%q\n", reflect.TypeOf([]byte{}).PkgPath()) // Unnamed: ""
So you have 2 tools available to you: Type.Name() to tell if the type is a named type, and Type.PkgPath() to tell if the type is not predeclared and is a named type.
But care must be taken. If you use your own, named type in a type literal to construct a new type (e.g. []A), that will be an unnamed type (if you don't use the type keyword to construct a new, named type):
type ASlice []A
fmt.Printf("%q\n", reflect.TypeOf([]A{}).PkgPath()) // Also unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(ASlice{}).PkgPath()) // Named: "main"
What can you do in such cases? You may use Type.Elem() to get the type's element type, if type's Kind is Array, Chan, Map, Ptr, or Slice (else Type.Elem() panics):
fmt.Printf("%q\n", reflect.TypeOf([]A{}).Elem().Name()) // Element type: "A"
fmt.Printf("%q\n", reflect.TypeOf([]A{}).Elem().PkgPath()) // Which is named, so: "main"
Summary
Type.PkgPath() can be used to "filter out" predeclared and unnamed types. If PkgPath() returns a non-empty string, you can be sure it's a "custom" type. If it returns an empty string, it still may be an unnamed type (in which case Type.Name() returns "") constructed from a "custom" type; for that you may use Type.Elem() to see if it is constructed from a "custom" type, which may have to be applied recursively:
// [][]A -> Elem() -> []A which is still unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf([][]A{}).Elem().PkgPath())
// [][]A -> Elem() -> []A -> Elem() -> A which is named: "main"
fmt.Printf("%q\n", reflect.TypeOf([][]A{}).Elem().Elem().PkgPath())
Try all the examples on the Go Playground.
Special case #1: Anonymous struct types
There is also the case of an anonymous struct type which is unnamed, but it may have a field of a "custom" type. This case can be handled by iterating over the fields of the struct type and performing the same check on each field, and if any of them is found to be a "custom" type, we can claim the whole struct type to be "custom".
Special case #2: Map types
In case of maps we may consider an unnamed map type "custom" if any of its key or value type is "custom".
The value type of a map can be queried with the above mentioned Type.Elem() method, and the key type of a map can be queried with the Type.Key() method - we also have to check this in case of maps.
Example implementation
func isCustom(t reflect.Type) bool {
if t.PkgPath() != "" {
return true
}
if k := t.Kind(); k == reflect.Array || k == reflect.Chan || k == reflect.Map ||
k == reflect.Ptr || k == reflect.Slice {
return isCustom(t.Elem()) || k == reflect.Map && isCustom(t.Key())
} else if k == reflect.Struct {
for i := t.NumField() - 1; i >= 0; i-- {
if isCustom(t.Field(i).Type) {
return true
}
}
}
return false
}
Testing it (try it on the Go Playground):
type K int
var i int = 2
fmt.Println(isCustom(reflect.TypeOf(""))) // false
fmt.Println(isCustom(reflect.TypeOf(int(2)))) // false
fmt.Println(isCustom(reflect.TypeOf([]int{}))) // false
fmt.Println(isCustom(reflect.TypeOf(struct{ i int }{}))) // false
fmt.Println(isCustom(reflect.TypeOf(&i))) // false
fmt.Println(isCustom(reflect.TypeOf(map[string]int{}))) // false
fmt.Println(isCustom(reflect.TypeOf(A{}))) // true
fmt.Println(isCustom(reflect.TypeOf(&A{}))) // true
fmt.Println(isCustom(reflect.TypeOf([]A{}))) // true
fmt.Println(isCustom(reflect.TypeOf([][]A{}))) // true
fmt.Println(isCustom(reflect.TypeOf(struct{ a A }{}))) // true
fmt.Println(isCustom(reflect.TypeOf(map[K]int{}))) // true
fmt.Println(isCustom(reflect.TypeOf(map[string]K{}))) // true

Resources