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