1package structtag
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"strconv"
8	"strings"
9)
10
11var (
12	errTagSyntax      = errors.New("bad syntax for struct tag pair")
13	errTagKeySyntax   = errors.New("bad syntax for struct tag key")
14	errTagValueSyntax = errors.New("bad syntax for struct tag value")
15
16	errKeyNotSet      = errors.New("tag key does not exist")
17	errTagNotExist    = errors.New("tag does not exist")
18	errTagKeyMismatch = errors.New("mismatch between key and tag.key")
19)
20
21// Tags represent a set of tags from a single struct field
22type Tags struct {
23	tags []*Tag
24}
25
26// Tag defines a single struct's string literal tag
27type Tag struct {
28	// Key is the tag key, such as json, xml, etc..
29	// i.e: `json:"foo,omitempty". Here key is: "json"
30	Key string
31
32	// Name is a part of the value
33	// i.e: `json:"foo,omitempty". Here name is: "foo"
34	Name string
35
36	// Options is a part of the value. It contains a slice of tag options i.e:
37	// `json:"foo,omitempty". Here options is: ["omitempty"]
38	Options []string
39}
40
41// Parse parses a single struct field tag and returns the set of tags.
42func Parse(tag string) (*Tags, error) {
43	var tags []*Tag
44
45	hasTag := tag != ""
46
47	// NOTE(arslan) following code is from reflect and vet package with some
48	// modifications to collect all necessary information and extend it with
49	// usable methods
50	for tag != "" {
51		// Skip leading space.
52		i := 0
53		for i < len(tag) && tag[i] == ' ' {
54			i++
55		}
56		tag = tag[i:]
57		if tag == "" {
58			break
59		}
60
61		// Scan to colon. A space, a quote or a control character is a syntax
62		// error. Strictly speaking, control chars include the range [0x7f,
63		// 0x9f], not just [0x00, 0x1f], but in practice, we ignore the
64		// multi-byte control characters as it is simpler to inspect the tag's
65		// bytes than the tag's runes.
66		i = 0
67		for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
68			i++
69		}
70
71		if i == 0 {
72			return nil, errTagKeySyntax
73		}
74		if i+1 >= len(tag) || tag[i] != ':' {
75			return nil, errTagSyntax
76		}
77		if tag[i+1] != '"' {
78			return nil, errTagValueSyntax
79		}
80
81		key := string(tag[:i])
82		tag = tag[i+1:]
83
84		// Scan quoted string to find value.
85		i = 1
86		for i < len(tag) && tag[i] != '"' {
87			if tag[i] == '\\' {
88				i++
89			}
90			i++
91		}
92		if i >= len(tag) {
93			return nil, errTagValueSyntax
94		}
95
96		qvalue := string(tag[:i+1])
97		tag = tag[i+1:]
98
99		value, err := strconv.Unquote(qvalue)
100		if err != nil {
101			return nil, errTagValueSyntax
102		}
103
104		res := strings.Split(value, ",")
105		name := res[0]
106		options := res[1:]
107		if len(options) == 0 {
108			options = nil
109		}
110
111		tags = append(tags, &Tag{
112			Key:     key,
113			Name:    name,
114			Options: options,
115		})
116	}
117
118	if hasTag && len(tags) == 0 {
119		return nil, nil
120	}
121
122	return &Tags{
123		tags: tags,
124	}, nil
125}
126
127// Get returns the tag associated with the given key. If the key is present
128// in the tag the value (which may be empty) is returned. Otherwise the
129// returned value will be the empty string. The ok return value reports whether
130// the tag exists or not (which the return value is nil).
131func (t *Tags) Get(key string) (*Tag, error) {
132	for _, tag := range t.tags {
133		if tag.Key == key {
134			return tag, nil
135		}
136	}
137
138	return nil, errTagNotExist
139}
140
141// Set sets the given tag. If the tag key already exists it'll override it
142func (t *Tags) Set(tag *Tag) error {
143	if tag.Key == "" {
144		return errKeyNotSet
145	}
146
147	added := false
148	for i, tg := range t.tags {
149		if tg.Key == tag.Key {
150			added = true
151			t.tags[i] = tag
152		}
153	}
154
155	if !added {
156		// this means this is a new tag, add it
157		t.tags = append(t.tags, tag)
158	}
159
160	return nil
161}
162
163// AddOptions adds the given option for the given key. If the option already
164// exists it doesn't add it again.
165func (t *Tags) AddOptions(key string, options ...string) {
166	for i, tag := range t.tags {
167		if tag.Key != key {
168			continue
169		}
170
171		for _, opt := range options {
172			if !tag.HasOption(opt) {
173				tag.Options = append(tag.Options, opt)
174			}
175		}
176
177		t.tags[i] = tag
178	}
179}
180
181// DeleteOptions deletes the given options for the given key
182func (t *Tags) DeleteOptions(key string, options ...string) {
183	hasOption := func(option string) bool {
184		for _, opt := range options {
185			if opt == option {
186				return true
187			}
188		}
189		return false
190	}
191
192	for i, tag := range t.tags {
193		if tag.Key != key {
194			continue
195		}
196
197		var updated []string
198		for _, opt := range tag.Options {
199			if !hasOption(opt) {
200				updated = append(updated, opt)
201			}
202		}
203
204		tag.Options = updated
205		t.tags[i] = tag
206	}
207}
208
209// Delete deletes the tag for the given keys
210func (t *Tags) Delete(keys ...string) {
211	hasKey := func(key string) bool {
212		for _, k := range keys {
213			if k == key {
214				return true
215			}
216		}
217		return false
218	}
219
220	var updated []*Tag
221	for _, tag := range t.tags {
222		if !hasKey(tag.Key) {
223			updated = append(updated, tag)
224		}
225	}
226
227	t.tags = updated
228}
229
230// Tags returns a slice of tags. The order is the original tag order unless it
231// was changed.
232func (t *Tags) Tags() []*Tag {
233	return t.tags
234}
235
236// Tags returns a slice of tags. The order is the original tag order unless it
237// was changed.
238func (t *Tags) Keys() []string {
239	var keys []string
240	for _, tag := range t.tags {
241		keys = append(keys, tag.Key)
242	}
243	return keys
244}
245
246// String reassembles the tags into a valid literal tag field representation
247func (t *Tags) String() string {
248	tags := t.Tags()
249	if len(tags) == 0 {
250		return ""
251	}
252
253	var buf bytes.Buffer
254	for i, tag := range t.Tags() {
255		buf.WriteString(tag.String())
256		if i != len(tags)-1 {
257			buf.WriteString(" ")
258		}
259	}
260	return buf.String()
261}
262
263// HasOption returns true if the given option is available in options
264func (t *Tag) HasOption(opt string) bool {
265	for _, tagOpt := range t.Options {
266		if tagOpt == opt {
267			return true
268		}
269	}
270
271	return false
272}
273
274// Value returns the raw value of the tag, i.e. if the tag is
275// `json:"foo,omitempty", the Value is "foo,omitempty"
276func (t *Tag) Value() string {
277	options := strings.Join(t.Options, ",")
278	if options != "" {
279		return fmt.Sprintf(`%s,%s`, t.Name, options)
280	}
281	return t.Name
282}
283
284// String reassembles the tag into a valid tag field representation
285func (t *Tag) String() string {
286	return fmt.Sprintf(`%s:%q`, t.Key, t.Value())
287}
288
289// GoString implements the fmt.GoStringer interface
290func (t *Tag) GoString() string {
291	template := `{
292		Key:    '%s',
293		Name:   '%s',
294		Option: '%s',
295	}`
296
297	if t.Options == nil {
298		return fmt.Sprintf(template, t.Key, t.Name, "nil")
299	}
300
301	options := strings.Join(t.Options, ",")
302	return fmt.Sprintf(template, t.Key, t.Name, options)
303}
304
305func (t *Tags) Len() int {
306	return len(t.tags)
307}
308
309func (t *Tags) Less(i int, j int) bool {
310	return t.tags[i].Key < t.tags[j].Key
311}
312
313func (t *Tags) Swap(i int, j int) {
314	t.tags[i], t.tags[j] = t.tags[j], t.tags[i]
315}
316