Skip to content
This repository was archived by the owner on Jan 3, 2023. It is now read-only.

Commit 6387fd4

Browse files
committed
update enum API.
Use a global RegisterEnum function to allow registering enum interfaces. Deprecate old lcs.EnumTypeUser interface.
1 parent 2f548ec commit 6387fd4

File tree

6 files changed

+201
-62
lines changed

6 files changed

+201
-62
lines changed

README.md

+40-41
Original file line numberDiff line numberDiff line change
@@ -96,54 +96,53 @@ type MyStruct struct {
9696

9797
### Enum types
9898

99-
Enum types are defined using interfaces.
99+
Enum types are golang interfaces.
100100

101-
Enum types can only be struct fields, with "enum" tags. Stand-alone enum types are not supported.
101+
(The [old enum API](https://github.com/the729/lcs/blob/v0.1.4/README.md#enum-types) is deprecated.)
102102

103103
```golang
104-
// isOption is an enum type. It has a dummy function to identify its variants.
105-
type isOption interface {
106-
isOption()
104+
// Enum1 is an enum type.
105+
type Enum1 interface {
106+
isEnum1() // optional: a dummy function to identify its variants.
107107
}
108108

109-
// *Option0, Option1 and Option2 are variants of isOption
110-
// Use pointer for (non-empty) struct.
111-
type Option0 struct {
109+
// *Enum1Opt0, Enum1Opt1, Enum1Opt2, Enum1Opt3 are variants of Enum1
110+
// Use pointer for non-empty struct.
111+
// Use empty struct for a variant without contents.
112+
type Enum1Opt0 struct {
112113
Data uint32
113114
}
114-
type Option1 struct {} // Empty enum variant
115-
type Option2 bool
116-
// Variants should implement isOption
117-
func (*Option0) isOption() {}
118-
func (Option1) isOption() {}
119-
func (Option2) isOption() {}
120-
121-
// MyStruct contains the enum type Option
122-
type MyStruct struct {
123-
Name string
124-
Option isOption `lcs:"enum=dummy"` // tag in "enum=name" format
125-
List2D [][]isOption `lcs:"enum=dummy"` // support multi-dim slices
115+
type Enum1Opt1 bool
116+
type Enum1Opt2 []byte
117+
type Enum1Opt3 []Enum1 // self reference is OK
118+
119+
// Variants should implement Enum1
120+
func (*Enum1Opt0) isEnum1() {}
121+
func (Enum1Opt1) isEnum1() {}
122+
func (Enum1Opt2) isEnum1() {}
123+
func (Enum1Opt3) isEnum1() {}
124+
125+
// Register Enum1 with LCS. Will be available globaly.
126+
var _ = lcs.RegisterEnum(
127+
// nil pointer to the enum interface type:
128+
(*Enum1)(nil),
129+
// zero-values of each variants
130+
(*Enum1Opt0)(nil),
131+
Enum1Opt1(false),
132+
Enum1Opt2(nil),
133+
Enum1Opt3(nil),
134+
)
135+
136+
// Usage: Marshal the enum alone, must use pointer
137+
e1 := Enum1(Enum1Opt1(true))
138+
bytes, err := lcs.Marshal(&e1)
139+
140+
// Use Enum1 within other structs
141+
type Wrapper struct {
142+
Enum Enum1
126143
}
144+
bytes, err := lcs.Marshal(&Wrapper{
145+
Enum: Enum1Opt1(true),
146+
})
127147

128-
// EnumTypes implement lcs.EnumTypeUser. It returns all the ingredients that can be
129-
// used for all enum fields in the receiver struct type.
130-
func (*Option) EnumTypes() []EnumVariant {
131-
return []EnumVariant{
132-
{
133-
Name: "dummy", // name should match the tags
134-
Value: 0, // numeric value of this variant
135-
Template: (*Option0)(nil), // zero value of this variant type
136-
},
137-
{
138-
Name: "dummy",
139-
Value: 1,
140-
Template: Option1{},
141-
},
142-
{
143-
Name: "dummy",
144-
Value: 2,
145-
Template: Option2(false),
146-
},
147-
}
148-
}
149148
```

decode.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -222,16 +222,16 @@ func (d *Decoder) decodeString(rv reflect.Value, fixedLen int) (err error) {
222222
}
223223

224224
func (d *Decoder) decodeInterface(rv reflect.Value, enumVariants map[EnumKeyType]reflect.Type) (err error) {
225-
if enumVariants == nil {
226-
return errors.New("enum variants not defined for interface: " + rv.Type().String())
227-
}
228225
typeVal, err := readVarUint(d.r, 28)
229226
if err != nil {
230227
return
231228
}
232-
tpl, ok := enumVariants[typeVal]
229+
tpl, ok := enumGetTypeByIdx(rv.Type(), typeVal)
233230
if !ok {
234-
return fmt.Errorf("enum variant value %d unknown for interface: %s", typeVal, rv.Type())
231+
tpl, ok = enumVariants[typeVal]
232+
if !ok {
233+
return fmt.Errorf("enum variant value %d unknown for interface: %s", typeVal, rv.Type())
234+
}
235235
}
236236
if tpl.Kind() == reflect.Ptr {
237237
rv1 := reflect.New(tpl.Elem())

encode.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,22 @@ func (e *Encoder) encodeSlice(rv reflect.Value, enumVariants map[reflect.Type]En
7777
}
7878

7979
func (e *Encoder) encodeInterface(rv reflect.Value, enumVariants map[reflect.Type]EnumKeyType) (err error) {
80-
if enumVariants == nil {
81-
return errors.New("enum variants not defined for interface: " + rv.Type().String())
82-
}
8380
if rv.IsNil() {
8481
return errors.New("non-optional enum value is nil")
8582
}
86-
rv = rv.Elem()
87-
ev, ok := enumVariants[rv.Type()]
83+
84+
ev, ok := enumGetIdxByType(rv.Type(), rv.Elem().Type())
85+
rvReal := rv.Elem()
8886
if !ok {
89-
return errors.New("enum variants not defined for type: " + rv.Type().String())
87+
ev, ok = enumVariants[rvReal.Type()]
88+
if !ok {
89+
return errors.New("enum " + rv.Type().String() + " does not have variant of type " + rvReal.Type().String())
90+
}
9091
}
9192
if _, err = writeVarUint(e.w, ev); err != nil {
9293
return
9394
}
94-
if err = e.encode(rv, nil, 0); err != nil {
95+
if err = e.encode(rvReal, nil, 0); err != nil {
9596
return err
9697
}
9798
return nil

enum.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package lcs
2+
3+
import (
4+
"reflect"
5+
)
6+
7+
var regEnumTypeToIdx map[reflect.Type]map[reflect.Type]EnumKeyType
8+
var regEnumIdxToType map[reflect.Type][]reflect.Type
9+
10+
// RegisterEnum register an enum type with its available variants. If the enum type
11+
// was registered, it will be overwriten.
12+
//
13+
// This function panics on errors. The returned error is always nil.
14+
func RegisterEnum(enumTypePtr interface{}, types ...interface{}) (err error) {
15+
rEnumType := reflect.TypeOf(enumTypePtr)
16+
if rEnumType.Kind() != reflect.Ptr {
17+
panic("enumType should be a pointer to a nil interface")
18+
}
19+
rEnumType = rEnumType.Elem()
20+
if rEnumType.Kind() != reflect.Interface {
21+
panic("enumType should be a pointer to a nil interface")
22+
}
23+
if regEnumTypeToIdx == nil {
24+
regEnumTypeToIdx = make(map[reflect.Type]map[reflect.Type]uint64)
25+
}
26+
if regEnumIdxToType == nil {
27+
regEnumIdxToType = make(map[reflect.Type][]reflect.Type)
28+
}
29+
regEnumIdxToType[rEnumType] = make([]reflect.Type, 0, len(types))
30+
regEnumTypeToIdx[rEnumType] = make(map[reflect.Type]uint64)
31+
for i, t := range types {
32+
rType := reflect.TypeOf(t)
33+
if !rType.Implements(rEnumType) {
34+
panic(rType.String() + " does not implement " + rEnumType.String())
35+
}
36+
regEnumIdxToType[rEnumType] = append(regEnumIdxToType[rEnumType], rType)
37+
regEnumTypeToIdx[rEnumType][rType] = EnumKeyType(i)
38+
}
39+
// log.Printf("registered: %v", rEnumType.String())
40+
return
41+
}
42+
43+
func enumGetTypeByIdx(enumType reflect.Type, idx EnumKeyType) (reflect.Type, bool) {
44+
// log.Printf("enumGetTypeByIdx: %v", enumType.String())
45+
m, ok := regEnumIdxToType[enumType]
46+
if !ok {
47+
return nil, false
48+
}
49+
if len(m) <= int(idx) {
50+
return nil, false
51+
}
52+
return m[int(idx)], true
53+
}
54+
55+
func enumGetIdxByType(enumType, vType reflect.Type) (EnumKeyType, bool) {
56+
// log.Printf("enumGetIdxByType: %v", enumType.String())
57+
m, ok := regEnumTypeToIdx[enumType]
58+
if !ok {
59+
return 0, false
60+
}
61+
idx, ok := m[vType]
62+
return idx, ok
63+
}

enum_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package lcs
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
type Enum1 interface {
10+
isEnum1()
11+
}
12+
13+
type Enum1Opt0 struct {
14+
Data uint32
15+
}
16+
type Enum1Opt1 bool
17+
type Enum1Opt2 []byte
18+
type Enum1Opt3 []Enum1
19+
20+
func (*Enum1Opt0) isEnum1() {}
21+
func (Enum1Opt1) isEnum1() {}
22+
func (Enum1Opt2) isEnum1() {}
23+
func (Enum1Opt3) isEnum1() {}
24+
25+
func TestInterfaceAsEnum(t *testing.T) {
26+
RegisterEnum((*Enum1)(nil),
27+
(*Enum1Opt0)(nil),
28+
Enum1Opt1(false),
29+
Enum1Opt2(nil),
30+
Enum1Opt3(nil),
31+
)
32+
e0 := Enum1(&Enum1Opt0{3})
33+
e1 := Enum1(Enum1Opt1(true))
34+
e2 := Enum1(Enum1Opt2([]byte{0x11, 0x22}))
35+
e3 := Enum1(Enum1Opt3([]Enum1{e0, e1}))
36+
runTest(t, []*testCase{
37+
{
38+
v: &e0,
39+
b: hexMustDecode("00 03000000"),
40+
name: "struct pointer",
41+
},
42+
{
43+
v: &e1,
44+
b: hexMustDecode("01 01"),
45+
name: "bool",
46+
},
47+
{
48+
v: &e2,
49+
b: hexMustDecode("02 02 1122"),
50+
name: "[]byte",
51+
},
52+
{
53+
v: &e3,
54+
b: hexMustDecode("03 02 00 03000000 01 01"),
55+
name: "enum slice of self",
56+
},
57+
})
58+
}
59+
60+
func TestRegisterNonInterfacePtrShouldPanic(t *testing.T) {
61+
assert.Panics(t, func() {
62+
RegisterEnum(uint32(0))
63+
})
64+
assert.Panics(t, func() {
65+
RegisterEnum(Enum1(nil))
66+
})
67+
assert.Panics(t, func() {
68+
RegisterEnum((*Enum1Opt0)(nil))
69+
})
70+
}
71+
72+
func TestRegisterWrongVariantShouldPanic(t *testing.T) {
73+
assert.Panics(t, func() {
74+
RegisterEnum((*Enum1)(nil), uint32(0))
75+
})
76+
}

example_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,20 @@ func (TxnArgU64) isTransactionArg() {}
6868
func (TxnArgAddress) isTransactionArg() {}
6969
func (TxnArgString) isTransactionArg() {}
7070

71+
// Register TransactionArgument with LCS. Will be available globaly.
72+
var _ = lcs.RegisterEnum(
73+
// pointer to enum interface:
74+
(*TransactionArgument)(nil),
75+
// zero-value of variants:
76+
TxnArgU64(0), TxnArgAddress([32]byte{}), TxnArgString(""),
77+
)
78+
7179
type Program struct {
7280
Code []byte
73-
Args []TransactionArgument `lcs:"enum=txn_arg"`
81+
Args []TransactionArgument
7482
Modules [][]byte
7583
}
7684

77-
func (*Program) EnumTypes() []lcs.EnumVariant {
78-
return []lcs.EnumVariant{
79-
{"txn_arg", 0, TxnArgU64(0)},
80-
{"txn_arg", 1, TxnArgAddress([32]byte{})},
81-
{"txn_arg", 2, TxnArgString("")},
82-
}
83-
}
84-
8585
func ExampleMarshal_libra_program() {
8686
prog := &Program{
8787
Code: []byte("move"),

0 commit comments

Comments
 (0)