-
Notifications
You must be signed in to change notification settings - Fork 2
/
conv.go
120 lines (110 loc) · 3.08 KB
/
conv.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//
// conv.go
//
// Copyright (c) 2016 Junpei Kawamoto
//
// This software is released under the MIT License.
//
// http://opensource.org/licenses/mit-license.php
//
package structpbconv
import (
"fmt"
"reflect"
"strings"
"github.com/golang/protobuf/ptypes/struct"
)
// tagKey defines a structure tag name for ConvertStructPB.
const tagKey = "structpb"
// Convert converts a structpb.Struct object to a concrete object.
func Convert(src *structpb.Struct, dst interface{}) error {
return convertStruct(src, reflect.ValueOf(dst))
}
func toPrimitive(src *structpb.Value) (reflect.Value, bool) {
switch t := src.GetKind().(type) {
case *structpb.Value_BoolValue:
return reflect.ValueOf(t.BoolValue), true
case *structpb.Value_NullValue:
return reflect.ValueOf(nil), true
case *structpb.Value_NumberValue:
return reflect.ValueOf(t.NumberValue), true
case *structpb.Value_StringValue:
return reflect.ValueOf(t.StringValue), true
default:
return reflect.Value{}, false
}
}
func convertValue(src *structpb.Value, dest reflect.Value) error {
dst := reflect.Indirect(dest)
if v, ok := toPrimitive(src); ok {
if !v.Type().AssignableTo(dst.Type()) {
if !v.Type().ConvertibleTo(dst.Type()) {
return fmt.Errorf("cannot assign %T to %s", src.GetKind(), dst.Type())
}
v = v.Convert(dst.Type())
}
dst.Set(v)
return nil
}
switch t := src.GetKind().(type) {
case *structpb.Value_ListValue:
return convertList(t.ListValue, dst)
case *structpb.Value_StructValue:
return convertStruct(t.StructValue, dst)
default:
return fmt.Errorf("unsuported value: %T", src.GetKind())
}
}
func convertList(src *structpb.ListValue, dest reflect.Value) error {
dst := reflect.Indirect(dest)
if dst.Kind() != reflect.Slice {
return fmt.Errorf("cannot convert %T to %s", src, dst.Type())
}
values := src.GetValues()
elemType := dst.Type().Elem()
converted := make([]reflect.Value, len(values))
for i, value := range values {
element := reflect.New(elemType).Elem()
if err := convertValue(value, element); err != nil {
return err
}
converted[i] = element
}
dst.Set(reflect.Append(dst, converted...))
return nil
}
func convertStruct(src *structpb.Struct, dest reflect.Value) error {
dst := reflect.Indirect(dest)
if dst.Kind() == reflect.Struct {
fields := src.GetFields()
for i := 0; i < dst.NumField(); i++ {
target := dst.Field(i)
field := dst.Type().Field(i)
name := field.Tag.Get(tagKey)
if name == "" {
name = strings.ToLower(field.Name)
}
if v, ok := fields[name]; ok {
if err := convertValue(v, target); err != nil {
return err
}
}
}
return nil
} else if dst.Kind() == reflect.Map {
elemType := dst.Type().Elem()
mapType := reflect.MapOf(reflect.TypeOf(string("")), elemType)
aMap := reflect.MakeMap(mapType)
fields := src.GetFields()
for key, value := range fields {
element := reflect.New(elemType).Elem()
if err := convertValue(value, element); err != nil {
return err
}
aMap.SetMapIndex(reflect.ValueOf(key), element)
}
dst.Set(aMap)
return nil
}
return fmt.Errorf("cannot convert %T to %s", src, dst.Type())
}