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