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