Skip to content
Merged
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
31 changes: 13 additions & 18 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 200
goconst:
Expand All @@ -16,7 +10,7 @@ linters-settings:
linters:
enable-all: true
disable:
- maligned
- recvcheck
- unparam
- lll
- gochecknoinits
Expand All @@ -29,17 +23,13 @@ linters:
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
Expand All @@ -52,10 +42,15 @@ linters:
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase
#- deadcode
#- interfacer
#- scopelint
#- varcheck
#- structcheck
#- golint
#- nosnakecase
#- maligned
#- goerr113
#- ifshort
#- gomnd
#- exhaustivestruct
25 changes: 25 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package jsonpointer

import (
"reflect"
)

type pointerError string

func (e pointerError) Error() string {
return string(e)
}

var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem()

const (
// ErrPointer is an error raised by the jsonpointer package
ErrPointer pointerError = "JSON pointer error"

// ErrInvalidStart states that a JSON pointer must start with a separator
ErrInvalidStart pointerError = `JSON pointer must be empty or start with a "` + pointerSeparator

// ErrUnsupportedValueType indicates that a value of the wrong type is being set
ErrUnsupportedValueType pointerError = "only structs, pointers, maps and slices are supported for setting values"
)
52 changes: 24 additions & 28 deletions pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,8 @@ import (
const (
emptyPointer = ``
pointerSeparator = `/`

invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator
notFound = `Can't find the pointer in the document`
)

var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem()

// JSONPointable is an interface for structs to implement when they need to customize the
// json pointer process
type JSONPointable interface {
Expand Down Expand Up @@ -80,7 +74,7 @@ func (p *Pointer) parse(jsonPointerString string) error {

if jsonPointerString != emptyPointer {
if !strings.HasPrefix(jsonPointerString, pointerSeparator) {
err = errors.New(invalidStart)
err = errors.Join(ErrInvalidStart, ErrPointer)
} else {
referenceTokens := strings.Split(jsonPointerString, pointerSeparator)
p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...)
Expand Down Expand Up @@ -128,7 +122,7 @@ func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvide
rValue := reflect.Indirect(reflect.ValueOf(node))
kind := rValue.Kind()
if isNil(node) {
return nil, kind, fmt.Errorf("nil value has not field %q", decodedToken)
return nil, kind, fmt.Errorf("nil value has no field %q: %w", decodedToken, ErrPointer)
}

switch typed := node.(type) {
Expand All @@ -146,7 +140,7 @@ func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvide
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return nil, kind, fmt.Errorf("object has no field %q", decodedToken)
return nil, kind, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
}
fld := rValue.FieldByName(nm)
return fld.Interface(), kind, nil
Expand All @@ -158,7 +152,7 @@ func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvide
if mv.IsValid() {
return mv.Interface(), kind, nil
}
return nil, kind, fmt.Errorf("object has no key %q", decodedToken)
return nil, kind, fmt.Errorf("object has no key %q: %w", decodedToken, ErrPointer)

case reflect.Slice:
tokenIndex, err := strconv.Atoi(decodedToken)
Expand All @@ -167,14 +161,14 @@ func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvide
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength-1, tokenIndex)
return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength-1, tokenIndex, ErrPointer)
}

elem := rValue.Index(tokenIndex)
return elem.Interface(), kind, nil

default:
return nil, kind, fmt.Errorf("invalid token reference %q", decodedToken)
return nil, kind, fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer)
}

}
Expand All @@ -194,7 +188,7 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *swag.NameP
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return fmt.Errorf("object has no field %q", decodedToken)
return fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
}
fld := rValue.FieldByName(nm)
if fld.IsValid() {
Expand All @@ -214,18 +208,18 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *swag.NameP
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength, tokenIndex, ErrPointer)
}

elem := rValue.Index(tokenIndex)
if !elem.CanSet() {
return fmt.Errorf("can't set slice index %s to %v", decodedToken, data)
return fmt.Errorf("can't set slice index %s to %v: %w", decodedToken, data, ErrPointer)
}
elem.Set(reflect.ValueOf(data))
return nil

default:
return fmt.Errorf("invalid token reference %q", decodedToken)
return fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer)
}

}
Expand All @@ -244,7 +238,6 @@ func (p *Pointer) get(node any, nameProvider *swag.NameProvider) (any, reflect.K
}

for _, token := range p.referenceTokens {

decodedToken := Unescape(token)

r, knd, err := getSingleImpl(node, decodedToken, nameProvider)
Expand All @@ -264,7 +257,10 @@ func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error {
knd := reflect.ValueOf(node).Kind()

if knd != reflect.Ptr && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array {
return errors.New("only structs, pointers, maps and slices are supported for setting values")
return errors.Join(
ErrUnsupportedValueType,
ErrPointer,
)
}

if nameProvider == nil {
Expand Down Expand Up @@ -307,7 +303,7 @@ func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error {
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return fmt.Errorf("object has no field %q", decodedToken)
return fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
}
fld := rValue.FieldByName(nm)
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr {
Expand All @@ -321,7 +317,7 @@ func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error {
mv := rValue.MapIndex(kv)

if !mv.IsValid() {
return fmt.Errorf("object has no key %q", decodedToken)
return fmt.Errorf("object has no key %q: %w", decodedToken, ErrPointer)
}
if mv.CanAddr() && mv.Kind() != reflect.Interface && mv.Kind() != reflect.Map && mv.Kind() != reflect.Slice && mv.Kind() != reflect.Ptr {
node = mv.Addr().Interface()
Expand All @@ -336,7 +332,7 @@ func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error {
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength, tokenIndex, ErrPointer)
}

elem := rValue.Index(tokenIndex)
Expand All @@ -347,7 +343,7 @@ func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error {
node = elem.Interface()

default:
return fmt.Errorf("invalid token reference %q", decodedToken)
return fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer)
}

}
Expand Down Expand Up @@ -404,10 +400,10 @@ func (p *Pointer) Offset(document string) (int64, error) {
return 0, err
}
default:
return 0, fmt.Errorf("invalid token %#v", tk)
return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer)
}
default:
return 0, fmt.Errorf("invalid token %#v", tk)
return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer)
}
}
return offset, nil
Expand Down Expand Up @@ -437,16 +433,16 @@ func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
return offset, nil
}
default:
return 0, fmt.Errorf("invalid token %#v", tk)
return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer)
}
}
return 0, fmt.Errorf("token reference %q not found", decodedToken)
return 0, fmt.Errorf("token reference %q not found: %w", decodedToken, ErrPointer)
}

func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
idx, err := strconv.Atoi(decodedToken)
if err != nil {
return 0, fmt.Errorf("token reference %q is not a number: %v", decodedToken, err)
return 0, fmt.Errorf("token reference %q is not a number: %v: %w", decodedToken, err, ErrPointer)
}
var i int
for i = 0; i < idx && dec.More(); i++ {
Expand All @@ -470,7 +466,7 @@ func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
}

if !dec.More() {
return 0, fmt.Errorf("token reference %q not found", decodedToken)
return 0, fmt.Errorf("token reference %q not found: %w", decodedToken, ErrPointer)
}
return dec.InputOffset(), nil
}
Expand Down
14 changes: 7 additions & 7 deletions pointer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (p pointableImpl) JSONLookup(token string) (any, error) {
if token == "some" {
return p.a, nil
}
return nil, fmt.Errorf("object has no field %q", token)
return nil, fmt.Errorf("object has no field %q: %w", token, ErrPointer)
}

type pointableMap map[string]string
Expand All @@ -215,7 +215,7 @@ func (p pointableMap) JSONLookup(token string) (any, error) {
return v, nil
}

return nil, fmt.Errorf("object has no key %q", token)
return nil, fmt.Errorf("object has no key %q: %w", token, ErrPointer)
}

func TestPointableInterface(t *testing.T) {
Expand Down Expand Up @@ -414,7 +414,7 @@ func (s settableDoc) JSONLookup(token string) (any, error) {
case "d":
return &s.Int, nil
default:
return nil, fmt.Errorf("%s is not a known field", token)
return nil, fmt.Errorf("%s is not a known field: %w", token, ErrPointer)
}
}

Expand Down Expand Up @@ -458,10 +458,10 @@ func (s *settableDoc) JSONSet(token string, data any) error {
s.Int.Value = int(dt)
return nil
default:
return fmt.Errorf("invalid type %T for %s", data, token)
return fmt.Errorf("invalid type %T for %s: %w", data, token, ErrPointer)
}
}
return fmt.Errorf("%s is not a known field", token)
return fmt.Errorf("%s is not a known field: %w", token, ErrPointer)
}

type settableColl struct {
Expand All @@ -480,7 +480,7 @@ func (s settableColl) JSONLookup(token string) (any, error) {
if tok, err := strconv.Atoi(token); err == nil {
return &s.Items[tok], nil
}
return nil, fmt.Errorf("%s is not a valid index", token)
return nil, fmt.Errorf("%s is not a valid index: %w", token, ErrPointer)
}

// JSONLookup implements an interface to customize json pointer lookup
Expand All @@ -489,7 +489,7 @@ func (s *settableColl) JSONSet(token string, data any) error {
_, err := SetForToken(s.Items, token, data)
return err
}
return fmt.Errorf("%s is not a valid index", token)
return fmt.Errorf("%s is not a valid index: %w", token, ErrPointer)
}

type settableCollItem struct {
Expand Down
Loading