Skip to content

Commit 061f7b4

Browse files
committed
chore(lint): reduced disabled linter, addressed a few code quality issues
* extended the set of enabled linters * improved readability of the set() method, which was too complex * added systematic checks on type assertion * removed impossible cases (type assertion _then_ same using reflect) * test: removed the use of global variables * test: improved the readability of tests * using t.Run() to label and hierarchize tests * using JSON test document as embedded asset Signed-off-by: Frederic BIDON <[email protected]>
1 parent e00aa64 commit 061f7b4

File tree

6 files changed

+449
-275
lines changed

6 files changed

+449
-275
lines changed

.golangci.yml

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,22 @@ version: "2"
22
linters:
33
default: all
44
disable:
5-
- cyclop
65
- depguard
7-
- errchkjson
86
- errorlint
97
- exhaustruct
10-
- forcetypeassert
118
- funlen
12-
- gochecknoglobals
13-
- gochecknoinits
14-
- gocognit
159
- godot
1610
- godox
1711
- gosmopolitan
18-
- inamedparam
1912
- ireturn
2013
- lll
21-
- musttag
22-
- nestif
2314
- nlreturn
2415
- nonamedreturns
2516
- noinlineerr
2617
- paralleltest
2718
- recvcheck
2819
- testpackage
29-
- thelper
3020
- tparallel
31-
- unparam
3221
- varnamelen
3322
- whitespace
3423
- wrapcheck
@@ -40,8 +29,10 @@ linters:
4029
goconst:
4130
min-len: 2
4231
min-occurrences: 3
32+
cyclop:
33+
max-complexity: 20
4334
gocyclo:
44-
min-complexity: 45
35+
min-complexity: 20
4536
exclusions:
4637
generated: lax
4738
presets:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# gojsonpointer
1+
# jsonpointer
22

33
<!-- Badges: status -->
44
[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url]
@@ -14,7 +14,7 @@
1414

1515
---
1616

17-
An implementation of JSON Pointer - Go language
17+
An implementation of JSON Pointer for golang, which supports go `struct`.
1818

1919
## Status
2020

pointer.go

Lines changed: 72 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,16 @@ const (
2020
pointerSeparator = `/`
2121
)
2222

23-
var (
24-
jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
25-
jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem()
26-
)
27-
2823
// JSONPointable is an interface for structs to implement when they need to customize the
2924
// json pointer process
3025
type JSONPointable interface {
31-
JSONLookup(string) (any, error)
26+
JSONLookup(key string) (any, error)
3227
}
3328

3429
// JSONSetable is an interface for structs to implement when they need to customize the
3530
// json pointer process
3631
type JSONSetable interface {
37-
JSONSet(string, any) error
32+
JSONSet(key string, value any) error
3833
}
3934

4035
// Pointer is a representation of a json pointer
@@ -174,7 +169,7 @@ func (p *Pointer) set(node, data any, nameProvider *jsonname.NameProvider) error
174169
nameProvider = jsonname.DefaultJSONNameProvider
175170
}
176171

177-
// Full document when empty
172+
// full document when empty
178173
if len(p.referenceTokens) == 0 {
179174
return nil
180175
}
@@ -185,81 +180,79 @@ func (p *Pointer) set(node, data any, nameProvider *jsonname.NameProvider) error
185180
decodedToken := Unescape(token)
186181

187182
if isLastToken {
188-
189183
return setSingleImpl(node, data, decodedToken, nameProvider)
190184
}
191185

192-
// Check for nil during traversal
193-
if isNil(node) {
194-
return fmt.Errorf("cannot traverse through nil value at %q: %w", decodedToken, ErrPointer)
186+
next, err := p.resolveNodeForToken(node, decodedToken, nameProvider)
187+
if err != nil {
188+
return err
195189
}
196190

197-
rValue := reflect.Indirect(reflect.ValueOf(node))
198-
kind := rValue.Kind()
191+
node = next
192+
}
199193

200-
if rValue.Type().Implements(jsonPointableType) {
201-
r, err := node.(JSONPointable).JSONLookup(decodedToken)
202-
if err != nil {
203-
return err
204-
}
205-
fld := reflect.ValueOf(r)
206-
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Pointer {
207-
node = fld.Addr().Interface()
208-
continue
209-
}
210-
node = r
211-
continue
194+
return nil
195+
}
196+
197+
func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvider *jsonname.NameProvider) (next any, err error) {
198+
// check for nil during traversal
199+
if isNil(node) {
200+
return nil, fmt.Errorf("cannot traverse through nil value at %q: %w", decodedToken, ErrPointer)
201+
}
202+
203+
pointable, ok := node.(JSONPointable)
204+
if ok {
205+
r, err := pointable.JSONLookup(decodedToken)
206+
if err != nil {
207+
return nil, err
212208
}
213209

214-
switch kind { //nolint:exhaustive
215-
case reflect.Struct:
216-
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
217-
if !ok {
218-
return fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
219-
}
220-
fld := rValue.FieldByName(nm)
221-
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Pointer {
222-
node = fld.Addr().Interface()
223-
continue
224-
}
225-
node = fld.Interface()
210+
fld := reflect.ValueOf(r)
211+
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Pointer {
212+
return fld.Addr().Interface(), nil
213+
}
226214

227-
case reflect.Map:
228-
kv := reflect.ValueOf(decodedToken)
229-
mv := rValue.MapIndex(kv)
215+
return r, nil
216+
}
230217

231-
if !mv.IsValid() {
232-
return fmt.Errorf("object has no key %q: %w", decodedToken, ErrPointer)
233-
}
234-
if mv.CanAddr() && mv.Kind() != reflect.Interface && mv.Kind() != reflect.Map && mv.Kind() != reflect.Slice && mv.Kind() != reflect.Pointer {
235-
node = mv.Addr().Interface()
236-
continue
237-
}
238-
node = mv.Interface()
218+
rValue := reflect.Indirect(reflect.ValueOf(node))
219+
kind := rValue.Kind()
239220

240-
case reflect.Slice:
241-
tokenIndex, err := strconv.Atoi(decodedToken)
242-
if err != nil {
243-
return err
244-
}
245-
sLength := rValue.Len()
246-
if tokenIndex < 0 || tokenIndex >= sLength {
247-
return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength, tokenIndex, ErrPointer)
248-
}
221+
switch kind { //nolint:exhaustive
222+
case reflect.Struct:
223+
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
224+
if !ok {
225+
return nil, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
226+
}
249227

250-
elem := rValue.Index(tokenIndex)
251-
if elem.CanAddr() && elem.Kind() != reflect.Interface && elem.Kind() != reflect.Map && elem.Kind() != reflect.Slice && elem.Kind() != reflect.Pointer {
252-
node = elem.Addr().Interface()
253-
continue
254-
}
255-
node = elem.Interface()
228+
return typeFromValue(rValue.FieldByName(nm)), nil
256229

257-
default:
258-
return fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer)
230+
case reflect.Map:
231+
kv := reflect.ValueOf(decodedToken)
232+
mv := rValue.MapIndex(kv)
233+
234+
if !mv.IsValid() {
235+
return nil, fmt.Errorf("object has no key %q: %w", decodedToken, ErrPointer)
259236
}
260-
}
261237

262-
return nil
238+
return typeFromValue(mv), nil
239+
240+
case reflect.Slice:
241+
tokenIndex, err := strconv.Atoi(decodedToken)
242+
if err != nil {
243+
return nil, errors.Join(err, ErrPointer)
244+
}
245+
246+
sLength := rValue.Len()
247+
if tokenIndex < 0 || tokenIndex >= sLength {
248+
return nil, fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength, tokenIndex, ErrPointer)
249+
}
250+
251+
return typeFromValue(rValue.Index(tokenIndex)), nil
252+
253+
default:
254+
return nil, fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer)
255+
}
263256
}
264257

265258
func isNil(input any) bool {
@@ -276,6 +269,14 @@ func isNil(input any) bool {
276269
}
277270
}
278271

272+
func typeFromValue(v reflect.Value) any {
273+
if v.CanAddr() && v.Kind() != reflect.Interface && v.Kind() != reflect.Map && v.Kind() != reflect.Slice && v.Kind() != reflect.Pointer {
274+
return v.Addr().Interface()
275+
}
276+
277+
return v.Interface()
278+
}
279+
279280
// GetForToken gets a value for a json pointer token 1 level deep
280281
func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) {
281282
return getSingleImpl(document, decodedToken, jsonname.DefaultJSONNameProvider)
@@ -348,14 +349,10 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
348349
return fmt.Errorf("cannot set field %q on nil value: %w", decodedToken, ErrPointer)
349350
}
350351

351-
if ns, ok := node.(JSONSetable); ok { // pointer impl
352+
if ns, ok := node.(JSONSetable); ok {
352353
return ns.JSONSet(decodedToken, data)
353354
}
354355

355-
if rValue.Type().Implements(jsonSetableType) {
356-
return node.(JSONSetable).JSONSet(decodedToken, data)
357-
}
358-
359356
switch rValue.Kind() { //nolint:exhaustive
360357
case reflect.Struct:
361358
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
@@ -499,8 +496,8 @@ const (
499496
)
500497

501498
var (
502-
encRefTokReplacer = strings.NewReplacer(encRefTok1, decRefTok1, encRefTok0, decRefTok0)
503-
decRefTokReplacer = strings.NewReplacer(decRefTok1, encRefTok1, decRefTok0, encRefTok0)
499+
encRefTokReplacer = strings.NewReplacer(encRefTok1, decRefTok1, encRefTok0, decRefTok0) //nolint:gochecknoglobals // it's okay to declare a replacer as a private global
500+
decRefTokReplacer = strings.NewReplacer(decRefTok1, encRefTok1, decRefTok0, encRefTok0) //nolint:gochecknoglobals // it's okay to declare a replacer as a private global
504501
)
505502

506503
// Unescape unescapes a json pointer reference token string to the original representation

0 commit comments

Comments
 (0)