diff --git a/pointer.go b/pointer.go index bed6b81..d975773 100644 --- a/pointer.go +++ b/pointer.go @@ -110,16 +110,36 @@ func SetForToken(document any, decodedToken string, value any) (any, error) { return document, setSingleImpl(document, value, decodedToken, swag.DefaultJSONNameProvider) } +func isNil(input any) bool { + if input == nil { + return true + } + + kind := reflect.TypeOf(input).Kind() + switch kind { //nolint:exhaustive + case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan: + return reflect.ValueOf(input).IsNil() + default: + return false + } +} + func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvider) (any, reflect.Kind, error) { rValue := reflect.Indirect(reflect.ValueOf(node)) kind := rValue.Kind() + if isNil(node) { + return nil, kind, fmt.Errorf("nil value has not field %q", decodedToken) + } - if rValue.Type().Implements(jsonPointableType) { - r, err := node.(JSONPointable).JSONLookup(decodedToken) + switch typed := node.(type) { + case JSONPointable: + r, err := typed.JSONLookup(decodedToken) if err != nil { return nil, kind, err } return r, kind, nil + case *any: // case of a pointer to interface, that is not resolved by reflect.Indirect + return getSingleImpl(*typed, decodedToken, nameProvider) } switch kind { //nolint:exhaustive diff --git a/pointer_test.go b/pointer_test.go index 5e27ab1..a789154 100644 --- a/pointer_test.go +++ b/pointer_test.go @@ -140,19 +140,56 @@ func TestIsEmpty(t *testing.T) { func TestGetSingle(t *testing.T) { const in = `/obj` - _, err := New(in) - require.NoError(t, err) - result, _, err := GetForToken(testDocumentJSON, "obj") - require.NoError(t, err) - assert.Len(t, result, TestNodeObjNBItems) + t.Run("should create a new JSON pointer", func(t *testing.T) { + _, err := New(in) + require.NoError(t, err) + }) - result, _, err = GetForToken(testStructJSONDoc, "Obj") - require.Error(t, err) - assert.Nil(t, result) + t.Run(`should find token "obj" in JSON`, func(t *testing.T) { + result, _, err := GetForToken(testDocumentJSON, "obj") + require.NoError(t, err) + assert.Len(t, result, TestNodeObjNBItems) + }) - result, _, err = GetForToken(testStructJSONDoc, "Obj2") - require.Error(t, err) - assert.Nil(t, result) + t.Run(`should find token "obj" in type alias interface`, func(t *testing.T) { + type alias interface{} + var in alias = testDocumentJSON + result, _, err := GetForToken(in, "obj") + require.NoError(t, err) + assert.Len(t, result, TestNodeObjNBItems) + }) + + t.Run(`should find token "obj" in pointer to interface`, func(t *testing.T) { + in := &testDocumentJSON + result, _, err := GetForToken(in, "obj") + require.NoError(t, err) + assert.Len(t, result, TestNodeObjNBItems) + }) + + t.Run(`should not find token "Obj" in struct`, func(t *testing.T) { + result, _, err := GetForToken(testStructJSONDoc, "Obj") + require.Error(t, err) + assert.Nil(t, result) + }) + + t.Run(`should not find token "Obj2" in struct`, func(t *testing.T) { + result, _, err := GetForToken(testStructJSONDoc, "Obj2") + require.Error(t, err) + assert.Nil(t, result) + }) + + t.Run(`should not find token in nil`, func(t *testing.T) { + result, _, err := GetForToken(nil, "obj") + require.Error(t, err) + assert.Nil(t, result) + }) + + t.Run(`should not find token in nil interface`, func(t *testing.T) { + var in interface{} + result, _, err := GetForToken(in, "obj") + require.Error(t, err) + assert.Nil(t, result) + }) } type pointableImpl struct {