Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -48,6 +51,8 @@ func (et ErrorType) String() string {
return "EinvalidQuery"
case ErequiredType:
return "ErequiredType"
case EinvalidValue:
return "EinvalidValue"
default:
return "Eunknown"
}
Expand Down Expand Up @@ -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())
}
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
11 changes: 5 additions & 6 deletions package_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

Expand Down
3 changes: 2 additions & 1 deletion pointer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(`{
Expand Down
211 changes: 211 additions & 0 deletions reflect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package dproxy

import (
"fmt"
"reflect"
"strconv"
)

// reflectProxy is a proxy using reflect.
type reflectProxy struct {
rv reflect.Value
p frame // parent
l string // label
}

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 {
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}
}

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.isArray() {
return nil, rp.typeError(Tarray)
}
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).Interface()
}
return v, nil
}

func (rp *reflectProxy) Map() (map[string]interface{}, error) {
if rp.rv.Kind() != reflect.Map {
return nil, rp.typeError(Tmap)
}
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.isArray() {
return rp.typeError(Tarray)
}
adrs := "[" + strconv.Itoa(n) + "]"
if n < 0 || n >= rp.rv.Len() {
return notfoundError(rp, adrs)
}
v := rp.rv.Index(n)
return newReflectProxy(v.Interface(), rp, 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 newReflectProxy(v.Interface(), rp, adrs)
case reflect.Struct:
v := rp.rv.FieldByName(k)
if !v.IsValid() {
return notfoundError(rp, adrs)
}
return newReflectProxy(v.Interface(), rp, 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, reflect.Slice:
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()),
}
}
}
90 changes: 90 additions & 0 deletions reflect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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")
}
})
}

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)")
}