summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-structr/util.go
blob: d8f227baf84880323ea74257d6858ae1e4be3b83 (plain)
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
package structr

import (
	"fmt"
	"reflect"
	"sync"
	"unicode"
	"unicode/utf8"

	"codeberg.org/gruf/go-byteutil"
	"codeberg.org/gruf/go-mangler"
)

// findField will search for a struct field with given set of names, where names is a len > 0 slice of names account for nesting.
func findField(t reflect.Type, names []string, allowZero bool) (sfield structfield, ok bool) {
	var (
		// isExported returns whether name is exported
		// from a package; can be func or struct field.
		isExported = func(name string) bool {
			r, _ := utf8.DecodeRuneInString(name)
			return unicode.IsUpper(r)
		}

		// popName pops the next name from
		// the provided slice of field names.
		popName = func() string {
			// Pop next name.
			name := names[0]
			names = names[1:]

			// Ensure valid name.
			if !isExported(name) {
				panicf("field is not exported: %s", name)
			}

			return name
		}

		// field is the iteratively searched-for
		// struct field value in below loop.
		field reflect.StructField
	)

	for len(names) > 0 {
		// Pop next name.
		name := popName()

		// Follow any ptrs leading to field.
		for t.Kind() == reflect.Pointer {
			t = t.Elem()
		}

		if t.Kind() != reflect.Struct {
			// The end type after following ptrs must be struct.
			panicf("field %s is not struct (ptr): %s", t, name)
		}

		// Look for next field by name.
		field, ok = t.FieldByName(name)
		if !ok {
			return
		}

		// Append next set of indices required to reach field.
		sfield.index = append(sfield.index, field.Index...)

		// Set the next type.
		t = field.Type
	}

	// Get final type mangler func.
	sfield.mangler = mangler.Get(t)

	if allowZero {
		var buf []byte

		// Allocate field instance.
		v := reflect.New(field.Type)
		v = v.Elem()

		// Serialize this zero value into buf.
		buf = sfield.mangler(buf, v.Interface())

		// Set zero value str.
		sfield.zero = string(buf)
	}

	return
}

// panicf provides a panic with string formatting.
func panicf(format string, args ...any) {
	panic(fmt.Sprintf(format, args...))
}

// bufpool provides a memory pool of byte
// buffers used when encoding key types.
var bufPool sync.Pool

// getBuf fetches buffer from memory pool.
func getBuf() *byteutil.Buffer {
	v := bufPool.Get()
	if v == nil {
		buf := new(byteutil.Buffer)
		buf.B = make([]byte, 0, 512)
		v = buf
	}
	return v.(*byteutil.Buffer)
}

// putBuf replaces buffer in memory pool.
func putBuf(buf *byteutil.Buffer) {
	if buf.Cap() > int(^uint16(0)) {
		return // drop large bufs
	}
	buf.Reset()
	bufPool.Put(buf)
}