Skip to content

Commit e4c97b3

Browse files
committed
chore(lint): more linting
* enabled a few more linters: better to configure a linter with more relaxed settings than default than to disable it altogether * more detailed godoc * fixed missing license mark in recently addd source file Signed-off-by: Frederic BIDON <[email protected]>
1 parent c653a59 commit e4c97b3

File tree

6 files changed

+109
-48
lines changed

6 files changed

+109
-48
lines changed

.golangci.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,9 @@ linters:
33
default: all
44
disable:
55
- depguard
6-
- errorlint
7-
- exhaustruct
86
- funlen
9-
- godot
107
- godox
11-
- gosmopolitan
12-
- ireturn
13-
- lll
8+
- exhaustruct
149
- nlreturn
1510
- nonamedreturns
1611
- noinlineerr
@@ -33,6 +28,11 @@ linters:
3328
max-complexity: 20
3429
gocyclo:
3530
min-complexity: 20
31+
exhaustive:
32+
default-signifies-exhaustive: true
33+
default-case-required: true
34+
lll:
35+
line-length: 180
3636
exclusions:
3737
generated: lax
3838
presets:
@@ -48,6 +48,7 @@ formatters:
4848
enable:
4949
- gofmt
5050
- goimports
51+
- gofumpt
5152
exclusions:
5253
generated: lax
5354
paths:

errors.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,33 @@
33

44
package jsonpointer
55

6+
import "fmt"
7+
68
type pointerError string
79

810
func (e pointerError) Error() string {
911
return string(e)
1012
}
1113

1214
const (
13-
// ErrPointer is an error raised by the jsonpointer package
15+
// ErrPointer is a sentinel error raised by all errors from this package.
1416
ErrPointer pointerError = "JSON pointer error"
1517

16-
// ErrInvalidStart states that a JSON pointer must start with a separator ("/")
18+
// ErrInvalidStart states that a JSON pointer must start with a separator ("/").
1719
ErrInvalidStart pointerError = `JSON pointer must be empty or start with a "` + pointerSeparator
1820

19-
// ErrUnsupportedValueType indicates that a value of the wrong type is being set
21+
// ErrUnsupportedValueType indicates that a value of the wrong type is being set.
2022
ErrUnsupportedValueType pointerError = "only structs, pointers, maps and slices are supported for setting values"
2123
)
24+
25+
func errNoKey(key string) error {
26+
return fmt.Errorf("object has no key %q: %w", key, ErrPointer)
27+
}
28+
29+
func errOutOfBounds(length, idx int) error {
30+
return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", length-1, idx, ErrPointer)
31+
}
32+
33+
func errInvalidReference(token string) error {
34+
return fmt.Errorf("invalid token reference %q: %w", token, ErrPointer)
35+
}

fuzz_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
14
package jsonpointer
25

36
import (

pointer.go

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

23-
// JSONPointable is an interface for structs to implement when they need to customize the
24-
// json pointer process
23+
// JSONPointable is an interface for structs to implement,
24+
// when they need to customize the json pointer process or want to avoid the use of reflection.
2525
type JSONPointable interface {
26+
// JSONLookup returns a value pointed at this (unescaped) key.
2627
JSONLookup(key string) (any, error)
2728
}
2829

29-
// JSONSetable is an interface for structs to implement when they need to customize the
30-
// json pointer process
30+
// JSONSetable is an interface for structs to implement,
31+
// when they need to customize the json pointer process or want to avoid the use of reflection.
3132
type JSONSetable interface {
33+
// JSONSet sets the value pointed at the (unescaped) key.
3234
JSONSet(key string, value any) error
3335
}
3436

35-
// Pointer is a representation of a json pointer
37+
// Pointer is a representation of a json pointer.
38+
//
39+
// Use [Pointer.Get] to retrieve a value or [Pointer.Set] to set a value.
40+
//
41+
// It works with any go type interpreted as a JSON document, which means:
42+
//
43+
// - if a type implements [JSONPointable], its [JSONPointable.JSONLookup] method is used to resolve [Pointer.Get]
44+
// - if a type implements [JSONSetable], its [JSONPointable.JSONSet] method is used to resolve [Pointer.Set]
45+
// - a go map[K]V is interpreted as an object, with type K assignable to a string
46+
// - a go slice []T is interpreted as an array
47+
// - a go struct is interpreted as an object, with exported fields interpreted as keys
48+
// - scalars (e.g. int, float64 ...), channels, functions and go arrays cannot be traversed
49+
//
50+
// For struct s resolved by reflection, key mappings honor the conventional struct tag `json`.
51+
//
52+
// Fields that do not specify a `json` tag, or specify an empty one, or are tagged as `json:"-"` are ignored.
53+
//
54+
// # Limitations
55+
//
56+
// - Unlike go standard marshaling, untagged fields do not default to the go field name and are ignored.
57+
// - anonymous embedded fields are not traversed
3658
type Pointer struct {
3759
referenceTokens []string
3860
}
3961

40-
// New creates a new json pointer for the given string
62+
// New creates a new json pointer from its string representation.
4163
func New(jsonPointerString string) (Pointer, error) {
4264
var p Pointer
4365
err := p.parse(jsonPointerString)
4466

4567
return p, err
4668
}
4769

48-
// Get uses the pointer to retrieve a value from a JSON document
70+
// Get uses the pointer to retrieve a value from a JSON document.
71+
//
72+
// It returns the value with its type as a [reflect.Kind] or an error.
4973
func (p *Pointer) Get(document any) (any, reflect.Kind, error) {
5074
return p.get(document, jsonname.DefaultJSONNameProvider)
5175
}
5276

53-
// Set uses the pointer to set a value from a JSON document
77+
// Set uses the pointer to set a value from a data type
78+
// that represent a JSON document.
79+
//
80+
// It returns the updated document.
5481
func (p *Pointer) Set(document any, value any) (any, error) {
5582
return document, p.set(document, value, jsonname.DefaultJSONNameProvider)
5683
}
5784

58-
// DecodedTokens returns the decoded tokens of this JSON pointer
85+
// DecodedTokens returns the decoded (unescaped) tokens of this JSON pointer.
5986
func (p *Pointer) DecodedTokens() []string {
6087
result := make([]string, 0, len(p.referenceTokens))
61-
for _, t := range p.referenceTokens {
62-
result = append(result, Unescape(t))
88+
for _, token := range p.referenceTokens {
89+
result = append(result, Unescape(token))
6390
}
91+
6492
return result
6593
}
6694

@@ -71,9 +99,8 @@ func (p *Pointer) IsEmpty() bool {
7199
return len(p.referenceTokens) == 0
72100
}
73101

74-
// String representation of a pointer
102+
// String representation of a pointer.
75103
func (p *Pointer) String() string {
76-
77104
if len(p.referenceTokens) == 0 {
78105
return emptyPointer
79106
}
@@ -112,13 +139,14 @@ func (p *Pointer) Offset(document string) (int64, error) {
112139
return offset, nil
113140
}
114141

115-
// "Constructor", parses the given string JSON pointer
142+
// "Constructor", parses the given string JSON pointer.
116143
func (p *Pointer) parse(jsonPointerString string) error {
117144
if jsonPointerString == emptyPointer {
118145
return nil
119146
}
120147

121148
if !strings.HasPrefix(jsonPointerString, pointerSeparator) {
149+
// non empty pointer must start with "/"
122150
return errors.Join(ErrInvalidStart, ErrPointer)
123151
}
124152

@@ -135,7 +163,7 @@ func (p *Pointer) get(node any, nameProvider *jsonname.NameProvider) (any, refle
135163

136164
kind := reflect.Invalid
137165

138-
// Full document when empty
166+
// full document when empty
139167
if len(p.referenceTokens) == 0 {
140168
return node, kind, nil
141169
}
@@ -161,6 +189,7 @@ func (p *Pointer) set(node, data any, nameProvider *jsonname.NameProvider) error
161189

162190
if knd != reflect.Pointer && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array {
163191
return errors.Join(
192+
fmt.Errorf("unexpected type: %T", node), //nolint:err113 // err wrapping is carried out by errors.Join, not fmt.Errorf.
164193
ErrUnsupportedValueType,
165194
ErrPointer,
166195
)
@@ -222,7 +251,7 @@ func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvide
222251
rValue := reflect.Indirect(reflect.ValueOf(node))
223252
kind := rValue.Kind()
224253

225-
switch kind { //nolint:exhaustive
254+
switch kind {
226255
case reflect.Struct:
227256
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
228257
if !ok {
@@ -236,7 +265,7 @@ func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvide
236265
mv := rValue.MapIndex(kv)
237266

238267
if !mv.IsValid() {
239-
return nil, fmt.Errorf("object has no key %q: %w", decodedToken, ErrPointer)
268+
return nil, errNoKey(decodedToken)
240269
}
241270

242271
return typeFromValue(mv), nil
@@ -249,13 +278,13 @@ func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvide
249278

250279
sLength := rValue.Len()
251280
if tokenIndex < 0 || tokenIndex >= sLength {
252-
return nil, fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength, tokenIndex, ErrPointer)
281+
return nil, errOutOfBounds(sLength, tokenIndex)
253282
}
254283

255284
return typeFromValue(rValue.Index(tokenIndex)), nil
256285

257286
default:
258-
return nil, fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer)
287+
return nil, errInvalidReference(decodedToken)
259288
}
260289
}
261290

@@ -265,7 +294,7 @@ func isNil(input any) bool {
265294
}
266295

267296
kind := reflect.TypeOf(input).Kind()
268-
switch kind { //nolint:exhaustive
297+
switch kind {
269298
case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Chan:
270299
return reflect.ValueOf(input).IsNil()
271300
default:
@@ -281,12 +310,12 @@ func typeFromValue(v reflect.Value) any {
281310
return v.Interface()
282311
}
283312

284-
// GetForToken gets a value for a json pointer token 1 level deep
313+
// GetForToken gets a value for a json pointer token 1 level deep.
285314
func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) {
286315
return getSingleImpl(document, decodedToken, jsonname.DefaultJSONNameProvider)
287316
}
288317

289-
// SetForToken gets a value for a json pointer token 1 level deep
318+
// SetForToken sets a value for a json pointer token 1 level deep.
290319
func SetForToken(document any, decodedToken string, value any) (any, error) {
291320
return document, setSingleImpl(document, value, decodedToken, jsonname.DefaultJSONNameProvider)
292321
}
@@ -309,13 +338,15 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro
309338
return getSingleImpl(*typed, decodedToken, nameProvider)
310339
}
311340

312-
switch kind { //nolint:exhaustive
341+
switch kind {
313342
case reflect.Struct:
314343
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
315344
if !ok {
316345
return nil, kind, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
317346
}
347+
318348
fld := rValue.FieldByName(nm)
349+
319350
return fld.Interface(), kind, nil
320351

321352
case reflect.Map:
@@ -325,7 +356,8 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro
325356
if mv.IsValid() {
326357
return mv.Interface(), kind, nil
327358
}
328-
return nil, kind, fmt.Errorf("object has no key %q: %w", decodedToken, ErrPointer)
359+
360+
return nil, kind, errNoKey(decodedToken)
329361

330362
case reflect.Slice:
331363
tokenIndex, err := strconv.Atoi(decodedToken)
@@ -334,14 +366,14 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro
334366
}
335367
sLength := rValue.Len()
336368
if tokenIndex < 0 || tokenIndex >= sLength {
337-
return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength-1, tokenIndex, ErrPointer)
369+
return nil, kind, errOutOfBounds(sLength, tokenIndex)
338370
}
339371

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

343375
default:
344-
return nil, kind, fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer)
376+
return nil, kind, errInvalidReference(decodedToken)
345377
}
346378
}
347379

@@ -357,7 +389,7 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
357389
return ns.JSONSet(decodedToken, data)
358390
}
359391

360-
switch rValue.Kind() { //nolint:exhaustive
392+
switch rValue.Kind() {
361393
case reflect.Struct:
362394
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
363395
if !ok {
@@ -381,7 +413,7 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
381413
}
382414
sLength := rValue.Len()
383415
if tokenIndex < 0 || tokenIndex >= sLength {
384-
return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", sLength, tokenIndex, ErrPointer)
416+
return errOutOfBounds(sLength, tokenIndex)
385417
}
386418

387419
elem := rValue.Index(tokenIndex)
@@ -392,7 +424,7 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
392424
return nil
393425

394426
default:
395-
return fmt.Errorf("invalid token reference %q: %w", decodedToken, ErrPointer)
427+
return errInvalidReference(decodedToken)
396428
}
397429
}
398430

@@ -430,7 +462,7 @@ func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
430462
func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
431463
idx, err := strconv.Atoi(decodedToken)
432464
if err != nil {
433-
return 0, fmt.Errorf("token reference %q is not a number: %v: %w", decodedToken, err, ErrPointer)
465+
return 0, fmt.Errorf("token reference %q is not a number: %w: %w", decodedToken, err, ErrPointer)
434466
}
435467
var i int
436468
for i = 0; i < idx && dec.More(); i++ {
@@ -461,6 +493,7 @@ func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
461493
}
462494

463495
// drainSingle drains a single level of object or array.
496+
//
464497
// The decoder has to guarantee the beginning delim (i.e. '{' or '[') has been consumed.
465498
func drainSingle(dec *json.Decoder) error {
466499
for dec.More() {
@@ -490,7 +523,7 @@ func drainSingle(dec *json.Decoder) error {
490523
return nil
491524
}
492525

493-
// Specific JSON pointer encoding here
526+
// JSON pointer encoding:
494527
// ~0 => ~
495528
// ~1 => /
496529
// ... and vice versa
@@ -507,12 +540,19 @@ var (
507540
decRefTokReplacer = strings.NewReplacer(decRefTok1, encRefTok1, decRefTok0, encRefTok0) //nolint:gochecknoglobals // it's okay to declare a replacer as a private global
508541
)
509542

510-
// Unescape unescapes a json pointer reference token string to the original representation
543+
// Unescape unescapes a json pointer reference token string to the original representation.
511544
func Unescape(token string) string {
512545
return encRefTokReplacer.Replace(token)
513546
}
514547

515-
// Escape escapes a pointer reference token string
548+
// Escape escapes a pointer reference token string.
549+
//
550+
// The JSONPointer specification defines "/" as a separator and "~" as an escape prefix.
551+
//
552+
// Keys containing such characters are escaped with the following rules:
553+
//
554+
// - "~" is escaped as "~0"
555+
// - "/" is escaped as "~1"
516556
func Escape(token string) string {
517557
return decRefTokReplacer.Replace(token)
518558
}

0 commit comments

Comments
 (0)