File: context.go

package info (click to toggle)
golang-github-oschwald-maxminddb-golang-v2 2.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,132 kB
  • sloc: perl: 557; makefile: 3
file content (136 lines) | stat: -rw-r--r-- 3,648 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package mmdberrors

import (
	"fmt"
	"strconv"
	"strings"
)

// ContextualError provides detailed error context with offset and path information.
// This is only allocated when an error actually occurs, ensuring zero allocation
// on the happy path.
type ContextualError struct {
	Err    error
	Path   string
	Offset uint
}

func (e ContextualError) Error() string {
	if e.Path != "" {
		return fmt.Sprintf("at offset %d, path %s: %v", e.Offset, e.Path, e.Err)
	}
	return fmt.Sprintf("at offset %d: %v", e.Offset, e.Err)
}

func (e ContextualError) Unwrap() error {
	return e.Err
}

// ErrorContextTracker is an optional interface that can be used to track
// path context for better error messages. Only used when explicitly enabled
// and only allocates when an error occurs.
type ErrorContextTracker interface {
	// BuildPath constructs a path string for the current decoder state.
	// This is only called when an error occurs, so allocation is acceptable.
	BuildPath() string
}

// WrapWithContext wraps an error with offset and optional path context.
// This function is designed to have zero allocation on the happy path -
// it only allocates when an error actually occurs.
func WrapWithContext(err error, offset uint, tracker ErrorContextTracker) error {
	if err == nil {
		return nil // Zero allocation - no error to wrap
	}

	// Only allocate when we actually have an error
	ctxErr := ContextualError{
		Offset: offset,
		Err:    err,
	}

	// Only build path if tracker is provided (opt-in behavior)
	if tracker != nil {
		ctxErr.Path = tracker.BuildPath()
	}

	return ctxErr
}

// PathBuilder helps build JSON-pointer-like paths efficiently.
// Only used when an error occurs, so allocations are acceptable here.
type PathBuilder struct {
	segments []string
}

// NewPathBuilder creates a new path builder.
func NewPathBuilder() *PathBuilder {
	return &PathBuilder{
		segments: make([]string, 0, 8), // Pre-allocate for common depth
	}
}

// BuildPath implements ErrorContextTracker interface.
func (p *PathBuilder) BuildPath() string {
	return p.Build()
}

// PushMap adds a map key to the path.
func (p *PathBuilder) PushMap(key string) {
	p.segments = append(p.segments, key)
}

// PushSlice adds a slice index to the path.
func (p *PathBuilder) PushSlice(index int) {
	p.segments = append(p.segments, strconv.Itoa(index))
}

// PrependMap adds a map key to the beginning of the path (for retroactive building).
func (p *PathBuilder) PrependMap(key string) {
	p.segments = append([]string{key}, p.segments...)
}

// PrependSlice adds a slice index to the beginning of the path (for retroactive building).
func (p *PathBuilder) PrependSlice(index int) {
	p.segments = append([]string{strconv.Itoa(index)}, p.segments...)
}

// Pop removes the last segment from the path.
func (p *PathBuilder) Pop() {
	if len(p.segments) > 0 {
		p.segments = p.segments[:len(p.segments)-1]
	}
}

// Build constructs the full path string.
func (p *PathBuilder) Build() string {
	if len(p.segments) == 0 {
		return "/"
	}
	return "/" + strings.Join(p.segments, "/")
}

// Reset clears all segments for reuse.
func (p *PathBuilder) Reset() {
	p.segments = p.segments[:0]
}

// ParseAndExtend parses an existing path and extends this builder with those segments.
// This is used for retroactive path building during error unwinding.
func (p *PathBuilder) ParseAndExtend(path string) {
	if path == "" || path == "/" {
		return
	}

	// Remove leading slash and split
	if path[0] == '/' {
		path = path[1:]
	}

	segments := strings.SplitSeq(path, "/")
	for segment := range segments {
		if segment != "" {
			p.segments = append(p.segments, segment)
		}
	}
}