From 12ad035b29f661aa2ae58e88ca526b3ed98cf428 Mon Sep 17 00:00:00 2001 From: MURAOKA Taro Date: Fri, 16 Mar 2018 17:44:58 +0900 Subject: [PATCH 1/5] WIP: reflect value proxy --- reflect.go | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 reflect.go diff --git a/reflect.go b/reflect.go new file mode 100644 index 0000000..b3429d4 --- /dev/null +++ b/reflect.go @@ -0,0 +1,176 @@ +package dproxy + +import ( + "reflect" + "strconv" +) + +// reflectProxy is a proxy using reflect. +type reflectProxy struct { + rv reflect.Value + p frame // parent + l string // label +} + +var _ Proxy = (*reflectProxy)(nil) + +func newReflectProxy(v interface{}, parent frame, label string) *reflectProxy { + rv := reflect.ValueOf(v) + return &reflectProxy{rv: rv, p: parent, l: label} +} + +func (rp *reflectProxy) parentFrame() frame { + return rp.p +} + +func (rp *reflectProxy) frameLabel() string { + return rp.l +} + +func (rp *reflectProxy) typeError(expected Type) *errorProxy { + return typeError(rp, expected, rp.rv.Interface()) +} + +func (rp *reflectProxy) Nil() bool { + return !rp.rv.IsValid() +} + +func (rp *reflectProxy) Value() (interface{}, error) { + return rp.rv.Interface(), nil +} + +func (rp *reflectProxy) Bool() (bool, error) { + switch rp.rv.Kind() { + case reflect.Bool: + return rp.rv.Bool(), nil + default: + return false, rp.typeError(Tbool) + } +} + +func (rp *reflectProxy) isInt() bool { + switch rp.rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return true + default: + return false + } +} + +func (rp *reflectProxy) Int64() (int64, error) { + if !rp.isInt() { + return 0, rp.typeError(Tint64) + } + return rp.rv.Int(), nil +} + +func (rp *reflectProxy) isFloat() bool { + switch rp.rv.Kind() { + case reflect.Float32, reflect.Float64: + return true + default: + return false + } +} + +func (rp *reflectProxy) Float64() (float64, error) { + if !rp.isFloat() { + return 0, rp.typeError(Tfloat64) + } + return rp.rv.Float(), nil +} + +func (rp *reflectProxy) String() (string, error) { + if rp.rv.Kind() != reflect.String { + return "", rp.typeError(Tstring) + } + return rp.rv.String(), nil +} + +func (rp *reflectProxy) Array() ([]interface{}, error) { + if rp.rv.Kind() != reflect.Array { + return nil, rp.typeError(Tarray) + } + // TODO: return value as []interface{} + return nil, nil +} + +func (rp *reflectProxy) Map() (map[string]interface{}, error) { + if rp.rv.Kind() != reflect.Map { + return nil, rp.typeError(Tmap) + } + // TODO: return value as map[string]interface{} + return nil, nil +} + +func (rp *reflectProxy) A(n int) Proxy { + if rp.rv.Kind() != reflect.Array { + return rp.typeError(Tarray) + } + adrs := "[" + strconv.Itoa(n) + "]" + if n < 0 || n >= rp.rv.Len() { + return notfoundError(rp, adrs) + } + return &reflectProxy{ + rv: rp.rv.Index(n), + p: rp, + l: adrs, + } +} + +func (rp *reflectProxy) M(k string) Proxy { + adrs := "." + k + switch rp.rv.Kind() { + case reflect.Map: + v := rp.rv.MapIndex(reflect.ValueOf(k)) + if !v.IsValid() { + return notfoundError(rp, adrs) + } + return &reflectProxy{rv: v, p: rp, l: adrs} + case reflect.Struct: + v := rp.rv.FieldByName(k) + if !v.IsValid() { + return notfoundError(rp, adrs) + } + return &reflectProxy{rv: v, p: rp, l: adrs} + default: + return rp.typeError(Tmap) + } +} + +func (rp *reflectProxy) P(q string) Proxy { + return pointer(rp, q) +} + +func (rp *reflectProxy) ProxySet() ProxySet { + // TODO: return proxy set for reflect vaue. + return nil +} + +func (rp *reflectProxy) Q(k string) ProxySet { + // TODO: return proxy set for queried value + return nil +} + +func (rp *reflectProxy) findJPT(t string) Proxy { + switch rp.rv.Kind() { + case reflect.Map, reflect.Struct: + return rp.M(t) + case reflect.Array: + n, err := strconv.ParseUint(t, 10, 0) + if err != nil { + return &errorProxy{ + errorType: EinvalidIndex, + parent: rp, + infoStr: err.Error(), + } + } + return rp.A(int(n)) + default: + return &errorProxy{ + errorType: EmapNorArray, + parent: rp, + actual: detectType(rp.rv.Interface()), + } + } +} From 89fefe1441b1706ade5396ac1e45b07055494b9e Mon Sep 17 00:00:00 2001 From: MURAOKA Taro Date: Fri, 16 Mar 2018 18:22:13 +0900 Subject: [PATCH 2/5] deref pointer --- error.go | 8 ++++++++ reflect.go | 25 +++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/error.go b/error.go index 45f89f1..ffa7ebd 100644 --- a/error.go +++ b/error.go @@ -30,6 +30,9 @@ const ( // ErequiredType means the type mismatch against user required one. // For example M() requires map, A() requires array. ErequiredType + + // EinvalidValue means proxy can't treat the value. + EinvalidValue ) func (et ErrorType) String() string { @@ -48,6 +51,8 @@ func (et ErrorType) String() string { return "EinvalidQuery" case ErequiredType: return "ErequiredType" + case EinvalidValue: + return "EinvalidValue" default: return "Eunknown" } @@ -205,6 +210,9 @@ func (p *errorProxy) Error() string { case ErequiredType: return fmt.Sprintf("not required types: required=%s actual=%s: %s", p.expected.String(), p.actual.String(), p.FullAddress()) + case EinvalidValue: + // FIXME: better error message. + return fmt.Sprintf("invalid value: %s: %s", p.infoStr, p.FullAddress()) default: return fmt.Sprintf("unexpected: %s", p.FullAddress()) } diff --git a/reflect.go b/reflect.go index b3429d4..c421506 100644 --- a/reflect.go +++ b/reflect.go @@ -1,6 +1,7 @@ package dproxy import ( + "fmt" "reflect" "strconv" ) @@ -14,8 +15,19 @@ type reflectProxy struct { var _ Proxy = (*reflectProxy)(nil) -func newReflectProxy(v interface{}, parent frame, label string) *reflectProxy { +func newReflectProxy(v interface{}, parent frame, label string) Proxy { rv := reflect.ValueOf(v) + for rv.Kind() == reflect.Ptr { + rv = rv.Elem() + if !rv.IsValid() { + return &errorProxy{ + errorType: EinvalidValue, + parent: parent, + label: label, + infoStr: fmt.Sprintf("%T", v), + } + } + } return &reflectProxy{rv: rv, p: parent, l: label} } @@ -111,11 +123,8 @@ func (rp *reflectProxy) A(n int) Proxy { if n < 0 || n >= rp.rv.Len() { return notfoundError(rp, adrs) } - return &reflectProxy{ - rv: rp.rv.Index(n), - p: rp, - l: adrs, - } + v := rp.rv.Index(n) + return newReflectProxy(v.Interface(), rp, adrs) } func (rp *reflectProxy) M(k string) Proxy { @@ -126,13 +135,13 @@ func (rp *reflectProxy) M(k string) Proxy { if !v.IsValid() { return notfoundError(rp, adrs) } - return &reflectProxy{rv: v, p: rp, l: adrs} + return newReflectProxy(v.Interface(), rp, adrs) case reflect.Struct: v := rp.rv.FieldByName(k) if !v.IsValid() { return notfoundError(rp, adrs) } - return &reflectProxy{rv: v, p: rp, l: adrs} + return newReflectProxy(v.Interface(), rp, adrs) default: return rp.typeError(Tmap) } From eb5c445bb41935194e481a046794e89b333ab212 Mon Sep 17 00:00:00 2001 From: MURAOKA Taro Date: Fri, 16 Mar 2018 19:08:30 +0900 Subject: [PATCH 3/5] impl. Array() and Map() for reflectProxy --- reflect.go | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/reflect.go b/reflect.go index c421506..a007dd2 100644 --- a/reflect.go +++ b/reflect.go @@ -15,6 +15,11 @@ type reflectProxy struct { var _ Proxy = (*reflectProxy)(nil) +// NewReflect creates a new proxy with reflect. +func NewReflect(v interface{}) Proxy { + return newReflectProxy(v, nil, "") +} + func newReflectProxy(v interface{}, parent frame, label string) Proxy { rv := reflect.ValueOf(v) for rv.Kind() == reflect.Ptr { @@ -100,23 +105,44 @@ func (rp *reflectProxy) String() (string, error) { } func (rp *reflectProxy) Array() ([]interface{}, error) { - if rp.rv.Kind() != reflect.Array { + if !rp.isArray() { return nil, rp.typeError(Tarray) } - // TODO: return value as []interface{} - return nil, nil + if v, ok := rp.rv.Interface().([]interface{}); ok { + return v, nil + } + v := make([]interface{}, rp.rv.Len()) + for i := range v { + v[i] = rp.rv.Index(i) + } + return v, nil } func (rp *reflectProxy) Map() (map[string]interface{}, error) { if rp.rv.Kind() != reflect.Map { return nil, rp.typeError(Tmap) } - // TODO: return value as map[string]interface{} - return nil, nil + if v, ok := rp.rv.Interface().(map[string]interface{}); ok { + return v, nil + } + v := map[string]interface{}{} + for _, k := range rp.rv.MapKeys() { + v[k.String()] = rp.rv.MapIndex(k) + } + return v, nil +} + +func (rp *reflectProxy) isArray() bool { + switch rp.rv.Kind() { + case reflect.Array, reflect.Slice: + return true + default: + return false + } } func (rp *reflectProxy) A(n int) Proxy { - if rp.rv.Kind() != reflect.Array { + if !rp.isArray() { return rp.typeError(Tarray) } adrs := "[" + strconv.Itoa(n) + "]" @@ -165,7 +191,7 @@ func (rp *reflectProxy) findJPT(t string) Proxy { switch rp.rv.Kind() { case reflect.Map, reflect.Struct: return rp.M(t) - case reflect.Array: + case reflect.Array, reflect.Slice: n, err := strconv.ParseUint(t, 10, 0) if err != nil { return &errorProxy{ From 3b69cd0f272ee36ce8a7861565ca54256280aef1 Mon Sep 17 00:00:00 2001 From: MURAOKA Taro Date: Mon, 19 Mar 2018 18:20:13 +0900 Subject: [PATCH 4/5] add tests --- reflect_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 reflect_test.go diff --git a/reflect_test.go b/reflect_test.go new file mode 100644 index 0000000..80d93ab --- /dev/null +++ b/reflect_test.go @@ -0,0 +1,71 @@ +package dproxy + +import "testing" + +func TestReflect_Readme(t *testing.T) { + v := parseJSON(`{ + "cities": [ "tokyo", 100, "osaka", 200, "hakata", 300 ], + "data": { + "custom": [ "male", 21, "female", 22 ] + } + }`) + + s, err := NewReflect(v).M("cities").A(0).String() + if s != "tokyo" { + t.Error("cities[0] must be \"tokyo\":", err) + } + + _, err = NewReflect(v).M("cities").A(0).Float64() + if err == nil { + t.Error("cities[0] (float64) must be failed:", err) + } + + n, err := NewReflect(v).M("cities").A(1).Float64() + if n != 100 { + t.Error("cities[1] must be 100:", err) + } + + s2, err := NewReflect(v).M("data").M("custom").A(2).String() + if s2 != "female" { + t.Error("data.custom[2] must be \"female\":", err) + } + + _, err = NewReflect(v).M("data").M("kustom").String() + if err == nil || err.Error() != "not found: data.kustom" { + t.Error("err is not \"not found: data.kustom\":", err) + } +} + +func TestReflect_MapBool(t *testing.T) { + v := parseJSON(`{ + "foo": true, + "bar": false + }`) + + // check "foo" + foo, err := NewReflect(v).M("foo").Bool() + if err != nil { + t.Error(err) + } else if foo != true { + t.Errorf("foo must be true") + } + + // check "bar" + bar, err := NewReflect(v).M("bar").Bool() + if err != nil { + t.Error(err) + } else if bar != false { + t.Errorf("bar must be false") + } +} + +func TestReflectMisc(t *testing.T) { + t.Run("nil", func(t *testing.T) { + if NewReflect(nil).Nil() == false { + t.Error("nil.Nil() should true but false") + } + if NewReflect("foo").Nil() == true { + t.Error("string.Nil() should false but true") + } + }) +} From a740f9607f22626ecb4f0aeec5c99dc4099a698b Mon Sep 17 00:00:00 2001 From: MURAOKA Taro Date: Mon, 13 Oct 2025 00:27:32 +0900 Subject: [PATCH 5/5] test Array method of Reflect based proxy --- go.mod | 4 +++- go.sum | 2 ++ package_test.go | 11 +++++------ pointer_test.go | 3 ++- reflect.go | 2 +- reflect_test.go | 19 +++++++++++++++++++ 6 files changed, 32 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d34f7be..358c38d 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/koron/go-dproxy -go 1.13 +go 1.21 + +require github.com/google/go-cmp v0.7.0 diff --git a/go.sum b/go.sum index e69de29..40e761a 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= diff --git a/package_test.go b/package_test.go index 88ba1f7..59fa462 100644 --- a/package_test.go +++ b/package_test.go @@ -1,16 +1,15 @@ package dproxy import ( - "fmt" - "reflect" "testing" + + "github.com/google/go-cmp/cmp" ) -func assertEquals(t *testing.T, actual, expected interface{}, format string, a ...interface{}) { +func assertEquals(t *testing.T, want, got any) { t.Helper() - if !reflect.DeepEqual(actual, expected) { - msg := fmt.Sprintf(format, a...) - t.Errorf("not equal: %s\nexpect=%+v\nactual=%+v", msg, expected, actual) + if d := cmp.Diff(want, got); d != "" { + t.Errorf("not equal -want +got\n%s", d) } } diff --git a/pointer_test.go b/pointer_test.go index db0069e..87a0739 100644 --- a/pointer_test.go +++ b/pointer_test.go @@ -33,13 +33,14 @@ func TestPointerInvalidQuery(t *testing.T) { func TestPointer(t *testing.T) { f := func(q string, d, expected interface{}) { + t.Helper() p := Pointer(d, q) v, err := p.Value() if err != nil { t.Errorf("Pointer:%q for %+v failed: %s", q, d, err) return } - assertEquals(t, v, expected, "Pointer:%q for %+v", q, d) + assertEquals(t, expected, v) } v := parseJSON(`{ diff --git a/reflect.go b/reflect.go index a007dd2..820f571 100644 --- a/reflect.go +++ b/reflect.go @@ -113,7 +113,7 @@ func (rp *reflectProxy) Array() ([]interface{}, error) { } v := make([]interface{}, rp.rv.Len()) for i := range v { - v[i] = rp.rv.Index(i) + v[i] = rp.rv.Index(i).Interface() } return v, nil } diff --git a/reflect_test.go b/reflect_test.go index 80d93ab..d7b5830 100644 --- a/reflect_test.go +++ b/reflect_test.go @@ -69,3 +69,22 @@ func TestReflectMisc(t *testing.T) { } }) } + +func TestReflect_Array(t *testing.T) { + got1, err := NewReflect(parseJSON(`{ + "cities": [ "tokyo", 100, "osaka", 200, "hakata", 300 ] + }`)).M("cities").Array() + if err != nil { + t.Fatalf("not array: %s", err) + } + assertEquals(t, []any{"tokyo", 100.0, "osaka", 200.0, "hakata", 300.0}, got1) + + got2, err := NewReflect([]string{"foo", "bar", "baz"}).Array() + if err != nil { + t.Fatalf("not array: %s", err) + } + assertEquals(t, []any{"foo", "bar", "baz"}, got2) + + _, err = NewReflect(0).Array() + assertError(t, err, "not matched types: expected=array actual=int64: (root)") +}