github.com/searKing/golang/go@v1.2.117/reflect/struct_tag.go (about) 1 // Copyright 2022 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package reflect 6 7 import ( 8 "bytes" 9 "errors" 10 "sort" 11 "strconv" 12 "strings" 13 14 strings_ "github.com/searKing/golang/go/strings" 15 ) 16 17 // cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go 18 var ( 19 errTagSyntax = errors.New("bad syntax for struct tag pair") 20 errTagKeySyntax = errors.New("bad syntax for struct tag key") 21 errTagValueSyntax = errors.New("bad syntax for struct tag value") 22 errTagValueSpace = errors.New("suspicious space in struct tag value") 23 errTagSpace = errors.New("key:\"value\" pairs not separated by spaces") 24 errTagDuplicatedKey = errors.New("duplicated for struct tag key") 25 26 errKeyNotSet = errors.New("tag key does not exist") 27 errTagNotExist = errors.New("tag does not exist") 28 ) 29 30 // A StructTag is the tag string in a struct field, as reflect.StructTag . 31 // 32 // By convention, tag strings are a concatenation of 33 // optionally space-separated key:"value" pairs. 34 // Each key is a non-empty string consisting of non-control 35 // characters other than space (U+0020 ' '), quote (U+0022 '"'), 36 // and colon (U+003A ':'). Each value is quoted using U+0022 '"' 37 // characters and Go string literal syntax. 38 type StructTag struct { 39 tagsByKey map[string]SubStructTag 40 orderedKeys []string 41 } 42 43 // ParseAstStructTag parses a single struct field tag of AST and returns the set of subTags. 44 // This code is based on the validateStructTag code in package 45 // tag `json:"name,omitempty"`, field.Tag.Value returned by AST 46 func ParseAstStructTag(tag string) (*StructTag, error) { 47 if tag != "" { 48 var err error 49 tag, err = strconv.Unquote(tag) 50 if err != nil { 51 return nil, err 52 } 53 } 54 return ParseStructTag(tag) 55 } 56 57 // ParseStructTag parses a single struct field tag and returns the set of subTags. 58 // This code is based on the validateStructTag code in package 59 // tag json:"name,omitempty", reflect.StructField.Tag returned by reflect 60 func ParseStructTag(tag string) (*StructTag, error) { 61 // This code is based on the validateStructTag code in package 62 // golang.org/x/tools/go/analysis/passes/structtag/structtag.go. 63 var st = StructTag{ 64 tagsByKey: map[string]SubStructTag{}, 65 } 66 67 // Code borrowed from golang.org/x/tools/go/analysis/passes/structtag/structtag.go 68 // Code borrowed from reflect/type.go 69 n := 0 70 for ; tag != ""; n++ { 71 if n > 0 && tag != "" && tag[0] != ' ' { 72 // More restrictive than reflect, but catches likely mistakes 73 // like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y". 74 return nil, errTagSpace 75 } 76 // Skip leading space. 77 i := 0 78 for i < len(tag) && tag[i] == ' ' { 79 i++ 80 } 81 tag = tag[i:] 82 if tag == "" { 83 break 84 } 85 86 // Scan to colon. A space, a quote or a control character is a syntax error. 87 // Strictly speaking, control chars include the range [0x7f, 0x9f], not just 88 // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters 89 // as it is simpler to inspect the tag's bytes than the tag's runes. 90 i = 0 91 for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { 92 i++ 93 } 94 if i == 0 { 95 return nil, errTagKeySyntax 96 } 97 if i+1 >= len(tag) || tag[i] != ':' { 98 return nil, errTagSyntax 99 } 100 if tag[i+1] != '"' { 101 return nil, errTagValueSyntax 102 } 103 key := tag[:i] 104 if !IsValidTagKey(key) { 105 return nil, errTagKeySyntax 106 } 107 tag = tag[i+1:] 108 109 // Scan quoted string to find value. 110 i = 1 111 for i < len(tag) && tag[i] != '"' { 112 if tag[i] == '\\' { 113 i++ 114 } 115 i++ 116 } 117 if i >= len(tag) { 118 return nil, errTagValueSyntax 119 } 120 qvalue := tag[:i+1] 121 tag = tag[i+1:] 122 123 value, err := strconv.Unquote(qvalue) 124 if err != nil { 125 return nil, errTagValueSyntax 126 } 127 128 if checkTagSpaces[key] { 129 switch key { 130 case "xml": 131 // If the first or last character in the XML tag is a space, it is 132 // suspicious. 133 if strings.Trim(value, " ") != value { 134 return nil, errTagValueSpace 135 } 136 137 // If there are multiple spaces, they are suspicious. 138 if strings.Count(value, " ") > 1 { 139 return nil, errTagValueSpace 140 } 141 142 // If there is no comma, skip the rest of the checks. 143 comma := strings.IndexRune(value, ',') 144 if comma < 0 { 145 break 146 } 147 148 // If the character before a comma is a space, this is suspicious. 149 if comma > 0 && value[comma-1] == ' ' { 150 return nil, errTagValueSpace 151 } 152 //value = value[comma+1:] 153 case "json": 154 // JSON allows using spaces in the name, so skip it. 155 comma := strings.IndexRune(value, ',') 156 if comma < 0 { 157 break 158 } 159 160 //value = value[comma+1:] 161 } 162 163 // If spaces exists in tag's value, it is suspicious. 164 if strings.IndexByte(value, ' ') >= 0 { 165 return nil, errTagValueSpace 166 } 167 } 168 169 res := strings.Split(value, ",") 170 name := res[0] 171 options := res[1:] 172 if len(options) == 0 { 173 options = nil 174 } 175 176 if _, has := st.tagsByKey[key]; has { 177 return nil, errTagDuplicatedKey 178 } 179 st.orderedKeys = append(st.orderedKeys, key) 180 st.tagsByKey[key] = SubStructTag{ 181 Key: key, 182 Name: name, 183 Options: options, 184 } 185 } 186 187 return &st, nil 188 } 189 190 // Get returns the tag associated with the given key. If the key is present 191 // in the tag the value (which may be empty) is returned. Otherwise the 192 // returned value will be the empty string. The ok return value reports whether 193 // the tag exists or not (which the return value is nil). 194 func (t StructTag) Get(key string) (tag SubStructTag, ok bool) { 195 if t.orderedKeys == nil { 196 return SubStructTag{}, false 197 } 198 tag, ok = t.tagsByKey[key] 199 return 200 } 201 202 func (t *StructTag) appendOrderedKeysIfNotPresent(key string) { 203 if t.tagsByKey == nil { 204 t.tagsByKey = make(map[string]SubStructTag) 205 } 206 if _, has := t.tagsByKey[key]; !has { 207 t.orderedKeys = append(t.orderedKeys, key) 208 } 209 } 210 211 // Set sets the given tag. If the tag key already exists it'll override it 212 func (t *StructTag) Set(subTag SubStructTag) error { 213 if subTag.Key == "" { 214 return errKeyNotSet 215 } 216 t.appendOrderedKeysIfNotPresent(subTag.Key) 217 t.tagsByKey[subTag.Key] = subTag 218 return nil 219 } 220 221 // SetName sets the given name for the given key. 222 func (t *StructTag) SetName(key string, name string) { 223 t.appendOrderedKeysIfNotPresent(key) 224 225 val, _ := t.tagsByKey[key] 226 val.Key = key 227 val.Name = name 228 t.tagsByKey[key] = val 229 } 230 231 // AddOptions adds the given option for the given key. 232 // It appends to any existing options associated with key. 233 func (t *StructTag) AddOptions(key string, options ...string) { 234 t.appendOrderedKeysIfNotPresent(key) 235 236 val, _ := t.tagsByKey[key] 237 val.Key = key 238 val.AddOptions(options...) 239 t.tagsByKey[key] = val 240 } 241 242 // DeleteOptions deletes the given options for the given key 243 func (t *StructTag) DeleteOptions(key string, options ...string) { 244 if t.tagsByKey == nil { 245 return 246 } 247 248 val, has := t.tagsByKey[key] 249 if !has { 250 return 251 } 252 val.DeleteOptions(options...) 253 t.tagsByKey[key] = val 254 } 255 256 // Delete deletes the tag for the given keys 257 func (t *StructTag) Delete(keys ...string) { 258 if t.tagsByKey == nil { 259 return 260 } 261 262 for _, key := range keys { 263 delete(t.tagsByKey, key) 264 strings_.SliceTrim(t.orderedKeys, key) 265 } 266 } 267 268 // Keys returns a slice of subTags. 269 func (t StructTag) Keys() []string { 270 var keys []string 271 for key := range t.tagsByKey { 272 keys = append(keys, key) 273 } 274 return keys 275 } 276 277 // SortedKeys returns a slice of subTags sorted by keys in increasing order. 278 func (t StructTag) SortedKeys() []string { 279 keys := t.Keys() 280 sort.Strings(keys) 281 return keys 282 } 283 284 // OrderKeys returns a slice of subTags with original order. 285 func (t StructTag) OrderKeys() []string { 286 return t.orderedKeys 287 } 288 289 // SelectedTags returns a slice of subTags in keys order. 290 func (t StructTag) SelectedTags(keys ...string) []SubStructTag { 291 if len(keys) == 0 { 292 return nil 293 } 294 var tags []SubStructTag 295 for _, key := range keys { 296 tag, has := t.tagsByKey[key] 297 if !has { 298 continue 299 } 300 tags = append(tags, tag) 301 } 302 return tags 303 } 304 305 // SelectString reassembles the subTags selected by keys into a valid literal tag field representation 306 // tag json:"name,omitempty", reflect.StructField.Tag returned by reflect 307 func (t StructTag) SelectString(keys ...string) string { 308 tags := t.SelectedTags(keys...) 309 if len(tags) == 0 { 310 return "" 311 } 312 313 var buf bytes.Buffer 314 for i, tag := range tags { 315 buf.WriteString(tag.String()) 316 if i != len(tags)-1 { 317 buf.WriteString(" ") 318 } 319 } 320 return buf.String() 321 } 322 323 // SelectAstString reassembles the subTags selected by keys into a valid literal tag field representation 324 // tag json:"name,omitempty", reflect.StructField.Tag returned by reflect 325 func (t StructTag) SelectAstString(keys ...string) string { 326 tag := t.SelectString(keys...) 327 if tag == "" { 328 return tag 329 } 330 return "`" + tag + "`" 331 } 332 333 // Tags returns a slice of subTags with original order. 334 func (t StructTag) Tags() []SubStructTag { 335 return t.SelectedTags(t.Keys()...) 336 } 337 338 // OrderedTags returns a slice of subTags sorted by keys in increasing order. 339 func (t StructTag) SortedTags() []SubStructTag { 340 return t.SelectedTags(t.SortedKeys()...) 341 } 342 343 // Tags returns a slice of subTags with original order. 344 func (t StructTag) OrderedTags() []SubStructTag { 345 return t.SelectedTags(t.OrderKeys()...) 346 } 347 348 // String reassembles the subTags into a valid literal tag field representation 349 // key is random. 350 // tag json:"name,omitempty", reflect.StructField.Tag returned by reflect 351 func (t StructTag) String() string { 352 return t.SelectString(t.Keys()...) 353 } 354 355 // SortedString reassembles the subTags into a valid literal tag field representation 356 // key is sorted by keys in increasing order. 357 // tag json:"name,omitempty", reflect.StructField.Tag returned by reflect 358 func (t StructTag) SortedString() string { 359 return t.SelectString(t.SortedKeys()...) 360 } 361 362 // OrderedString reassembles the subTags into a valid literal tag field representation 363 // key is in the original order. 364 // tag json:"name,omitempty", reflect.StructField.Tag returned by reflect 365 func (t StructTag) OrderedString() string { 366 return t.SelectString(t.OrderKeys()...) 367 } 368 369 // AstString reassembles the subTags into a valid literal ast tag field representation 370 // key is random. 371 // tag `json:"name,omitempty"`, field.Tag.Value returned by AST 372 func (t StructTag) AstString() string { 373 return t.SelectAstString(t.Keys()...) 374 } 375 376 // SortedAstString reassembles the subTags into a valid literal ast tag field representation 377 // key is sorted by keys in increasing order. 378 // tag `json:"name,omitempty"`, field.Tag.Value returned by AST 379 func (t StructTag) SortedAstString() string { 380 return t.SelectAstString(t.SortedKeys()...) 381 } 382 383 // OrderedAstString reassembles the subTags into a valid literal ast tag field representation 384 // key is in the original order. 385 // tag `json:"name,omitempty"`, field.Tag.Value returned by AST 386 func (t StructTag) OrderedAstString() string { 387 return t.SelectAstString(t.OrderKeys()...) 388 } 389 390 var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}