Compare commits

...

19 Commits

Author SHA1 Message Date
852f19598d util/reflect: fix protobuf field name detection
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 11:34:28 +03:00
6537b35773 util/reflect: add interface merging
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 01:19:37 +03:00
b733f1316f remove stale generate stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-16 17:36:27 +03:00
Renovate Bot
840af5574c fix(deps): update golang.org/x/net commit hash to e915ea6 2021-04-16 00:56:52 +00:00
Renovate Bot
56e5b7001c fix(deps): update golang.org/x/net commit hash to 0645797 2021-04-14 21:41:15 +00:00
Renovate Bot
11dc6fd752 fix(deps): update golang.org/x/net commit hash to afb366f 2021-04-10 11:09:36 +00:00
a2695d8699 util/reflect: rewrite struct merging with map
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-10 01:22:40 +03:00
618421de05 client: allow to set content-type for call
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-09 23:09:12 +03:00
Renovate Bot
30baaabd9f fix(deps): update golang.org/x/net commit hash to a5a99cb 2021-04-05 19:46:46 +00:00
df5bce1191 util/reflect: fix StructURLValues
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-03 11:50:23 +03:00
Renovate Bot
089d0fe4df fix(deps): update golang.org/x/net commit hash to 0fccb6f 2021-03-31 22:50:38 +00:00
a06f535303 util/reflect: add StructURLValues func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-01 00:30:26 +03:00
Renovate Bot
eba586a329 fix(deps): update golang.org/x/net commit hash to cb1fcc7 2021-03-31 09:19:27 +00:00
Renovate Bot
d74a8645e8 fix(deps): update golang.org/x/net commit hash to e572328 2021-03-31 00:52:53 +00:00
Renovate Bot
5a00786192 fix(deps): update golang.org/x/net commit hash to cd0ac97 2021-03-30 22:28:38 +00:00
Renovate Bot
b3e9941634 fix(deps): update golang.org/x/net commit hash to c8897c2 2021-03-30 16:02:28 +00:00
Renovate Bot
a5a5904302 fix(deps): update golang.org/x/net commit hash to 22f4162 2021-03-30 11:44:00 +00:00
Renovate Bot
a59832e57e fix(deps): update golang.org/x/net commit hash to df645c7 2021-03-30 05:11:12 +00:00
0e42033e7f meter/handler: more idiomatic option handling
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-29 17:51:44 +03:00
9 changed files with 914 additions and 603 deletions

15
.github/generate.sh vendored
View File

@@ -1,15 +0,0 @@
#!/bin/bash -e
find . -type f -name '*.pb.*.go' -o -name '*.pb.go' -a ! -name 'message.pb.go' -delete
PROTOS=$(find . -type f -name '*.proto' | grep -v proto/google/api)
mkdir -p proto/google/api
curl -s -o proto/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto
curl -s -o proto/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto
for PROTO in $PROTOS; do
echo $PROTO
protoc -I./proto -I. -I$(dirname $PROTO) --go-grpc_out=paths=source_relative:. --go_out=paths=source_relative:. --micro_out=paths=source_relative:. $PROTO
done
rm -r proto

View File

@@ -80,6 +80,8 @@ type CallOptions struct {
Backoff BackoffFunc
// Network name
Network string
// Content-Type
ContentType string
// CallWrappers call wrappers
CallWrappers []CallWrapper
// SelectOptions selector options
@@ -382,6 +384,13 @@ func PublishContext(ctx context.Context) PublishOption {
}
}
// WithContentType specifies call content type
func WithContentType(ct string) CallOption {
return func(o *CallOptions) {
o.ContentType = ct
}
}
// WithAddress sets the remote addresses to use rather than using service discovery
func WithAddress(a ...string) CallOption {
return func(o *CallOptions) {
@@ -486,16 +495,16 @@ func WithMessageContentType(ct string) MessageOption {
}
}
// WithContentType specifies request content type
func WithContentType(ct string) RequestOption {
// StreamingRequest specifies that request is streaming
func StreamingRequest(b bool) RequestOption {
return func(o *RequestOptions) {
o.Stream = b
}
}
// RequestContentType specifies request content type
func RequestContentType(ct string) RequestOption {
return func(o *RequestOptions) {
o.ContentType = ct
}
}
// StreamingRequest specifies that request is streaming
func StreamingRequest() RequestOption {
return func(o *RequestOptions) {
o.Stream = true
}
}

View File

@@ -1,3 +0,0 @@
package micro
//go:generate ./.github/generate.sh

2
go.mod
View File

@@ -9,5 +9,5 @@ require (
github.com/imdario/mergo v0.3.12
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34
golang.org/x/net v0.0.0-20210326220855-61e056675ecf
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d
)

8
go.sum
View File

@@ -10,12 +10,12 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
golang.org/x/net v0.0.0-20210326220855-61e056675ecf h1:WUcCxqQqDT0aXO4VnQbfMvp4zh7m1Gb2clVuHUAGGRE=
golang.org/x/net v0.0.0-20210326220855-61e056675ecf/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs=
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=

View File

@@ -5,6 +5,7 @@ import (
"context"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/meter"
)
@@ -14,18 +15,52 @@ var (
)
type handler struct {
meter meter.Meter
opts []meter.Option
opts Options
}
func NewHandler(meter meter.Meter, opts ...meter.Option) *handler {
return &handler{meter: meter, opts: opts}
type Option func(*Options)
type Options struct {
Meter meter.Meter
MeterOptions []meter.Option
Name string
}
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
func Name(name string) Option {
return func(o *Options) {
o.Name = name
}
}
func MeterOptions(opts ...meter.Option) Option {
return func(o *Options) {
o.MeterOptions = append(o.MeterOptions, opts...)
}
}
func NewOptions(opts ...Option) Options {
options := Options{Meter: meter.DefaultMeter}
for _, o := range opts {
o(&options)
}
return options
}
func NewHandler(opts ...Option) *handler {
options := NewOptions(opts...)
return &handler{opts: options}
}
func (h *handler) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
buf := bytes.NewBuffer(nil)
if err := h.meter.Write(buf, h.opts...); err != nil {
return err
if err := h.opts.Meter.Write(buf, h.opts.MeterOptions...); err != nil {
return errors.InternalServerError(h.opts.Name, "%v", err)
}
rsp.Data = buf.Bytes()

File diff suppressed because it is too large Load Diff

398
util/reflect/struct.go Normal file
View File

@@ -0,0 +1,398 @@
package reflect
import (
"errors"
"fmt"
"net/url"
"reflect"
"regexp"
"strings"
)
var (
// ErrInvalidParam specifies invalid url query params
ErrInvalidParam = errors.New("invalid url query param provided")
)
var (
bracketSplitter = regexp.MustCompile(`\[|\]`)
)
// StructFields returns slice of struct fields
func StructFields(src interface{}) ([]reflect.StructField, error) {
var fields []reflect.StructField
sv := reflect.ValueOf(src)
if sv.Kind() == reflect.Ptr {
sv = sv.Elem()
}
if sv.Kind() != reflect.Struct {
return nil, ErrInvalidStruct
}
typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 {
continue
}
if val.Kind() == reflect.Struct {
infields, err := StructFields(val.Interface())
if err != nil {
return nil, err
}
fields = append(fields, infields...)
} else {
fields = append(fields, fld)
}
}
return fields, nil
}
// CopyDefaults for a from b
// a and b should be pointers to the same kind of struct
func CopyDefaults(a, b interface{}) {
pt := reflect.TypeOf(a)
t := pt.Elem()
va := reflect.ValueOf(a).Elem()
vb := reflect.ValueOf(b).Elem()
for i := 0; i < t.NumField(); i++ {
aField := va.Field(i)
if aField.CanSet() {
bField := vb.Field(i)
aField.Set(bField)
}
}
}
// CopyFrom sets the public members of a from b
// a and b should be pointers to structs
// a can be a different type from b
// Only the Fields which have the same name and assignable type on a
// and b will be set.
func CopyFrom(a, b interface{}) {
ta := reflect.TypeOf(a).Elem()
tb := reflect.TypeOf(b).Elem()
va := reflect.ValueOf(a).Elem()
vb := reflect.ValueOf(b).Elem()
for i := 0; i < tb.NumField(); i++ {
bField := vb.Field(i)
tbField := tb.Field(i)
name := tbField.Name
aField := va.FieldByName(name)
taField, found := ta.FieldByName(name)
if found && aField.IsValid() && bField.IsValid() && aField.CanSet() && tbField.Type.AssignableTo(taField.Type) {
aField.Set(bField)
}
}
}
func StructURLValues(src interface{}, pref string, tags []string) (url.Values, error) {
data := url.Values{}
sv := reflect.ValueOf(src)
if sv.Kind() == reflect.Ptr {
sv = sv.Elem()
}
if sv.Kind() != reflect.Struct {
return nil, ErrInvalidStruct
}
typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 || !val.IsValid() {
continue
}
var t *tag
for _, tn := range tags {
ts, ok := fld.Tag.Lookup(tn)
if !ok {
continue
}
tp := strings.Split(ts, ",")
// special
switch tn {
case "protobuf": // special
t = &tag{key: tn, name: tp[3][5:], opts: append(tp[:3], tp[4:]...)}
default:
t = &tag{key: tn, name: tp[0], opts: tp[1:]}
}
if t.name != "" {
break
}
}
if t.name == "" {
// fallback to lowercase
t.name = strings.ToLower(fld.Name)
}
if pref != "" {
t.name = pref + "." + t.name
}
if !val.IsValid() || val.IsZero() {
continue
}
switch val.Kind() {
case reflect.Struct, reflect.Ptr:
if val.IsNil() {
continue
}
ndata, err := StructURLValues(val.Interface(), t.name, tags)
if err != nil {
return ndata, err
}
for k, v := range ndata {
data[k] = v
}
default:
switch val.Kind() {
case reflect.Slice:
for i := 0; i < val.Len(); i++ {
va := val.Index(i)
//if va.Type().Elem().Kind() != reflect.Ptr {
if va.Kind() != reflect.Ptr {
data.Set(t.name, fmt.Sprintf("%v", va.Interface()))
continue
}
switch va.Type().Elem().String() {
case "wrapperspb.BoolValue", "wrapperspb.BytesValue", "wrapperspb.StringValue":
if eva := reflect.Indirect(va).FieldByName("Value"); eva.IsValid() {
data.Add(t.name, fmt.Sprintf("%v", eva.Interface()))
}
case "wrapperspb.DoubleValue", "wrapperspb.FloatValue":
if eva := reflect.Indirect(va).FieldByName("Value"); eva.IsValid() {
data.Add(t.name, fmt.Sprintf("%v", eva.Interface()))
}
case "wrapperspb.Int32Value", "wrapperspb.Int64Value":
if eva := reflect.Indirect(va).FieldByName("Value"); eva.IsValid() {
data.Add(t.name, fmt.Sprintf("%v", eva.Interface()))
}
case "wrapperspb.UInt32Value", "wrapperspb.UInt64Value":
if eva := reflect.Indirect(va).FieldByName("Value"); eva.IsValid() {
data.Add(t.name, fmt.Sprintf("%v", eva.Interface()))
}
default:
data.Add(t.name, fmt.Sprintf("%v", val.Index(i).Interface()))
}
}
default:
data.Set(t.name, fmt.Sprintf("%v", val.Interface()))
}
}
}
return data, nil
}
// URLMap returns map of url query params
func URLMap(query string) (map[string]interface{}, error) {
var (
mp interface{} = make(map[string]interface{})
)
params := strings.Split(query, "&")
for _, part := range params {
tm, err := queryToMap(part)
if err != nil {
return nil, err
}
mp = merge(mp, tm)
}
return mp.(map[string]interface{}), nil
}
// FlattenMap expand key.subkey to nested map
func FlattenMap(a map[string]interface{}) map[string]interface{} {
// preprocess map
nb := make(map[string]interface{}, len(a))
for k, v := range a {
ps := strings.Split(k, ".")
if len(ps) == 1 {
nb[k] = v
continue
}
em := make(map[string]interface{})
em[ps[len(ps)-1]] = v
for i := len(ps) - 2; i > 0; i-- {
nm := make(map[string]interface{})
nm[ps[i]] = em
em = nm
}
if vm, ok := nb[ps[0]]; ok {
// nested map
nm := vm.(map[string]interface{})
for vk, vv := range em {
nm[vk] = vv
}
nb[ps[0]] = nm
} else {
nb[ps[0]] = em
}
}
return nb
}
/*
case reflect.String:
fn := func(c rune) bool { return c == ',' || c == ';' || c == ' ' }
slice := strings.FieldsFunc(vb.String(), fn)
if va.IsNil() {
va.Set(reflect.MakeSlice(va.Type(), len(slice), len(slice)))
}
*/
func btSplitter(str string) []string {
r := bracketSplitter.Split(str, -1)
for idx, s := range r {
if len(s) == 0 {
if len(r) > idx+1 {
copy(r[idx:], r[idx+1:])
r = r[:len(r)-1]
}
}
}
return r
}
// queryToMap turns something like a[b][c]=4 into
// map[string]interface{}{
// "a": map[string]interface{}{
// "b": map[string]interface{}{
// "c": 4,
// },
// },
// }
func queryToMap(param string) (map[string]interface{}, error) {
rawKey, rawValue, err := splitKeyAndValue(param)
if err != nil {
return nil, err
}
rawValue, err = url.QueryUnescape(rawValue)
if err != nil {
return nil, err
}
rawKey, err = url.QueryUnescape(rawKey)
if err != nil {
return nil, err
}
pieces := btSplitter(rawKey)
key := pieces[0]
// If len==1 then rawKey has no [] chars and we can just
// decode this as key=value into {key: value}
if len(pieces) == 1 {
return map[string]interface{}{
key: rawValue,
}, nil
}
// If len > 1 then we have something like a[b][c]=2
// so we need to turn this into {"a": {"b": {"c": 2}}}
// To do this we break our key into two pieces:
// a and b[c]
// and then we set {"a": queryToMap("b[c]", value)}
ret := make(map[string]interface{})
ret[key], err = queryToMap(buildNewKey(rawKey) + "=" + rawValue)
if err != nil {
return nil, err
}
// When URL params have a set of empty brackets (eg a[]=1)
// it is assumed to be an array. This will get us the
// correct value for the array item and return it as an
// []interface{} so that it can be merged properly.
if pieces[1] == "" {
temp := ret[key].(map[string]interface{})
ret[key] = []interface{}{temp[""]}
}
return ret, nil
}
// buildNewKey will take something like:
// origKey = "bar[one][two]"
// pieces = [bar one two ]
// and return "one[two]"
func buildNewKey(origKey string) string {
pieces := btSplitter(origKey)
ret := origKey[len(pieces[0])+1:]
ret = ret[:len(pieces[1])] + ret[len(pieces[1])+1:]
return ret
}
// splitKeyAndValue splits a URL param at the last equal
// sign and returns the two strings. If no equal sign is
// found, the ErrInvalidParam error is returned.
func splitKeyAndValue(param string) (string, string, error) {
li := strings.LastIndex(param, "=")
if li == -1 {
return "", "", ErrInvalidParam
}
return param[:li], param[li+1:], nil
}
// merge merges a with b if they are either both slices
// or map[string]interface{} types. Otherwise it returns b.
func merge(a interface{}, b interface{}) interface{} {
if av, aok := a.(map[string]interface{}); aok {
if bv, bok := b.(map[string]interface{}); bok {
return mergeMapIface(av, bv)
}
}
if av, aok := a.([]interface{}); aok {
if bv, bok := b.([]interface{}); bok {
return mergeSliceIface(av, bv)
}
}
va := reflect.ValueOf(a)
vb := reflect.ValueOf(b)
if (va.Type().Kind() == reflect.Slice) && (va.Type().Elem().Kind() == vb.Type().Kind() || vb.Type().ConvertibleTo(va.Type().Elem())) {
va = reflect.Append(va, vb.Convert(va.Type().Elem()))
return va.Interface()
}
return b
}
// mergeMap merges a with b, attempting to merge any nested
// values in nested maps but eventually overwriting anything
// in a that can't be merged with whatever is in b.
func mergeMapIface(a map[string]interface{}, b map[string]interface{}) map[string]interface{} {
for bK, bV := range b {
if aV, ok := a[bK]; ok {
if (reflect.ValueOf(aV).Type().Kind() == reflect.ValueOf(bV).Type().Kind()) ||
((reflect.ValueOf(aV).Type().Kind() == reflect.Slice) && reflect.ValueOf(aV).Type().Elem().Kind() == reflect.ValueOf(bV).Type().Kind()) {
nV := []interface{}{aV, bV}
a[bK] = nV
} else {
a[bK] = merge(a[bK], bV)
}
} else {
a[bK] = bV
}
}
return a
}
// mergeSlice merges a with b and returns the result.
func mergeSliceIface(a []interface{}, b []interface{}) []interface{} {
a = append(a, b...)
return a
}
type tag struct {
key string
name string
opts []string
}

View File

@@ -5,6 +5,24 @@ import (
"testing"
)
func TestStructURLValues(t *testing.T) {
type Str struct {
Name string `json:"name"`
Args []int `json:"args"`
Str *Str `json:"str"`
}
val := &Str{Name: "test_name", Args: []int{1, 2, 3}, Str: &Str{Name: "nested_name"}}
data, err := StructURLValues(val, "", []string{"json"})
if err != nil {
t.Fatal(err)
}
if data.Get("name") != "test_name" {
t.Fatalf("invalid data: %v", data)
}
}
func TestURLSliceVars(t *testing.T) {
u, err := url.Parse("http://localhost/v1/test/call/my_name?key=arg1&key=arg2&key=arg3")
if err != nil {