-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtag.go
124 lines (111 loc) · 3.74 KB
/
tag.go
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
package pic
import (
"fmt"
"reflect"
"strconv"
"strings"
"sync"
)
// Constants for tag parsing.
const (
tagKey = "pic"
tagSeparator = ","
ignoreTagValue = "-"
omitIndicator = 1
simpleIndicator = 2 //nolint:deadcode,varcheck // is unused but kept for clarity
occursIndicator = 3
)
// structFieldCache is a cache for struct field representations.
var structFieldCache sync.Map // map[reflect.Type]structRepresentation
// structRepresentation represents the structure of a struct, including its length and fields.
type structRepresentation struct {
totalLength int
fields []fieldRepresentation
}
// fieldRepresentation represents a field in a struct, including its set function, tag, and any errors.
type fieldRepresentation struct {
setFunc setValueFunc
tag tagRepresentation
err error
}
// tagRepresentation represents a tag in a struct field, including its start, end, occurs, length, and skip values.
type tagRepresentation struct {
start int
end int
occurs int
length int
skip bool
}
// parseTag parses a tag string and returns a tagRepresentation and any error.
// The tag string should be in the format `pic:"-"` to ignore the field, or `pic:"1,2"` or `pic:"1,2,2"` to specify the start, end, and optionally occurs values.
func parseTag(tag string) (*tagRepresentation, error) {
tagValues := &tagRepresentation{}
splitTag := strings.Split(tag, tagSeparator)
tagElements := len(splitTag)
if tagElements == omitIndicator {
if splitTag[0] != ignoreTagValue {
return nil, fmt.Errorf("single pic tag value provided, expected %s", ignoreTagValue)
}
tagValues.skip = true
return tagValues, nil
}
if tagElements == occursIndicator {
occurrences, err := strconv.Atoi(splitTag[2])
if err != nil {
return nil, fmt.Errorf("failed string->int conversion: %w", err)
}
tagValues.occurs = occurrences
}
var err error
tagValues.start, tagValues.end, tagValues.length, err = getSpread(splitTag)
if err != nil {
return nil, err
}
return tagValues, nil
}
// getSpread returns the start, end, and length of a tag's values.
func getSpread(splitTag []string) (int, int, int, error) {
start, err := strconv.Atoi(splitTag[0])
if err != nil {
return 0, 0, 0, fmt.Errorf("failed string->int conversion: %w", err)
}
end, err := strconv.Atoi(splitTag[1])
if err != nil {
return 0, 0, 0, fmt.Errorf("failed string->int conversion: %w", err)
}
length := end - (start - 1)
return start, end, length, nil
}
// makeStructRepresentation creates a structRepresentation for a given reflect.Type.
func makeStructRepresentation(structType reflect.Type) structRepresentation {
structRep := structRepresentation{
fields: make([]fieldRepresentation, structType.NumField()),
}
for fieldIndex := 0; fieldIndex < structType.NumField(); fieldIndex++ {
field := structType.Field(fieldIndex)
tagValues, err := parseTag(field.Tag.Get(tagKey))
if err != nil {
structRep.fields[fieldIndex].err = err
continue
}
structRep.fields[fieldIndex].tag = *tagValues
if tagValues.skip {
structRep.fields[fieldIndex].setFunc = skipSetValueFunc
} else {
structRep.fields[fieldIndex].setFunc = newSetValueFunc(field.Type, tagValues.length, tagValues.occurs)
}
if structRep.fields[fieldIndex].tag.end > structRep.totalLength {
structRep.totalLength = structRep.fields[fieldIndex].tag.end
}
}
return structRep
}
// cachedStructRepresentation creates a structRepresentation for a given reflect.Type and caches it to prevent duplicate work.
func cachedStructRepresentation(structType reflect.Type) structRepresentation {
if fieldRep, ok := structFieldCache.Load(structType); ok {
return fieldRep.(structRepresentation)
}
fieldRep := makeStructRepresentation(structType)
structFieldCache.Store(structType, fieldRep)
return fieldRep
}