File: struct.go

package info (click to toggle)
golang-github-goji-param 0.0~git20160927.d7f49fd-6
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid, trixie
  • size: 112 kB
  • sloc: makefile: 2
file content (121 lines) | stat: -rw-r--r-- 3,073 bytes parent folder | download | duplicates (2)
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
package param

import (
	"reflect"
	"strings"
	"sync"
)

// We decode a lot of structs (since it's the top-level thing this library
// decodes) and it takes a fair bit of work to reflect upon the struct to figure
// out what we want to do. Instead of doing this on every invocation, we cache
// metadata about each struct the first time we see it. The upshot is that we
// save some work every time. The downside is we are forced to briefly acquire
// a lock to access the cache in a thread-safe way. If this ever becomes a
// bottleneck, both the lock and the cache can be sharded or something.
type structCache map[string]cacheLine
type cacheLine struct {
	offset int
	parse  func(string, string, []string, reflect.Value)
}

var cacheLock sync.RWMutex
var cache = make(map[reflect.Type]structCache)

func cacheStruct(t reflect.Type) structCache {
	cacheLock.RLock()
	sc, ok := cache[t]
	cacheLock.RUnlock()

	if ok {
		return sc
	}

	// It's okay if two people build struct caches simultaneously
	sc = make(structCache)
	for i := 0; i < t.NumField(); i++ {
		sf := t.Field(i)
		// Only unexported fields have a PkgPath; we want to only cache
		// exported fields.
		if sf.PkgPath != "" && !sf.Anonymous {
			continue
		}
		name := extractName(sf)
		if name != "-" {
			sc[name] = cacheLine{i, extractHandler(t, sf)}
		}
	}

	cacheLock.Lock()
	cache[t] = sc
	cacheLock.Unlock()

	return sc
}

// Extract the name of the given struct field, looking at struct tags as
// appropriate.
func extractName(sf reflect.StructField) string {
	name := sf.Tag.Get("param")
	if name == "" {
		name = sf.Tag.Get("json")
		idx := strings.IndexRune(name, ',')
		if idx >= 0 {
			name = name[:idx]
		}
	}
	if name == "" {
		name = sf.Name
	}

	return name
}

func extractHandler(s reflect.Type, sf reflect.StructField) func(string, string, []string, reflect.Value) {
	if reflect.PtrTo(sf.Type).Implements(textUnmarshalerType) {
		return parseTextUnmarshaler
	}

	switch sf.Type.Kind() {
	case reflect.Bool:
		return parseBool
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return parseInt
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return parseUint
	case reflect.Float32, reflect.Float64:
		return parseFloat
	case reflect.Map:
		return parseMap
	case reflect.Ptr:
		return parsePtr
	case reflect.Slice:
		return parseSlice
	case reflect.String:
		return parseString
	case reflect.Struct:
		return parseStruct

	default:
		pebkac("struct %v has illegal field %q (type %v, kind %v).",
			s, sf.Name, sf.Type, sf.Type.Kind())
		return nil
	}
}

// We have to parse two types of structs: ones at the top level, whose keys
// don't have square brackets around them, and nested structs, which do.
func parseStructField(cache structCache, key, sk, keytail string, values []string, target reflect.Value) {
	l, ok := cache[sk]
	if !ok {
		panic(KeyError{
			FullKey: key,
			Key:     kpath(key, keytail),
			Type:    target.Type(),
			Field:   sk,
		})
	}
	f := target.Field(l.offset)

	l.parse(key, keytail, values, f)
}