github.com/wangyougui/gf/v2@v2.6.5/os/gstructs/gstructs_tag.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package gstructs
     8  
     9  import (
    10  	"reflect"
    11  	"strconv"
    12  
    13  	"github.com/wangyougui/gf/v2/errors/gcode"
    14  	"github.com/wangyougui/gf/v2/errors/gerror"
    15  	"github.com/wangyougui/gf/v2/util/gtag"
    16  )
    17  
    18  // ParseTag parses tag string into map.
    19  // For example:
    20  // ParseTag(`v:"required" p:"id" d:"1"`) => map[v:required p:id d:1].
    21  func ParseTag(tag string) map[string]string {
    22  	var (
    23  		key  string
    24  		data = make(map[string]string)
    25  	)
    26  	for tag != "" {
    27  		// Skip leading space.
    28  		i := 0
    29  		for i < len(tag) && tag[i] == ' ' {
    30  			i++
    31  		}
    32  		tag = tag[i:]
    33  		if tag == "" {
    34  			break
    35  		}
    36  		// Scan to colon. A space, a quote or a control character is a syntax error.
    37  		// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
    38  		// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
    39  		// as it is simpler to inspect the tag's bytes than the tag's runes.
    40  		i = 0
    41  		for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
    42  			i++
    43  		}
    44  		if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
    45  			break
    46  		}
    47  		key = tag[:i]
    48  		tag = tag[i+1:]
    49  
    50  		// Scan quoted string to find value.
    51  		i = 1
    52  		for i < len(tag) && tag[i] != '"' {
    53  			if tag[i] == '\\' {
    54  				i++
    55  			}
    56  			i++
    57  		}
    58  		if i >= len(tag) {
    59  			break
    60  		}
    61  		quotedValue := tag[:i+1]
    62  		tag = tag[i+1:]
    63  		value, err := strconv.Unquote(quotedValue)
    64  		if err != nil {
    65  			panic(gerror.WrapCodef(gcode.CodeInvalidParameter, err, `error parsing tag "%s"`, tag))
    66  		}
    67  		data[key] = gtag.Parse(value)
    68  	}
    69  	return data
    70  }
    71  
    72  // TagFields retrieves and returns struct tags as []Field from `pointer`.
    73  //
    74  // The parameter `pointer` should be type of struct/*struct.
    75  //
    76  // Note that,
    77  // 1. It only retrieves the exported attributes with first letter upper-case from struct.
    78  // 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
    79  func TagFields(pointer interface{}, priority []string) ([]Field, error) {
    80  	return getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{})
    81  }
    82  
    83  // TagMapName retrieves and returns struct tags as map[tag]attribute from `pointer`.
    84  //
    85  // The parameter `pointer` should be type of struct/*struct.
    86  //
    87  // Note that,
    88  // 1. It only retrieves the exported attributes with first letter upper-case from struct.
    89  // 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
    90  // 3. If one field has no specified tag, it uses its field name as result map key.
    91  func TagMapName(pointer interface{}, priority []string) (map[string]string, error) {
    92  	fields, err := TagFields(pointer, priority)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	tagMap := make(map[string]string, len(fields))
    97  	for _, field := range fields {
    98  		tagMap[field.TagValue] = field.Name()
    99  	}
   100  	return tagMap, nil
   101  }
   102  
   103  // TagMapField retrieves struct tags as map[tag]Field from `pointer`, and returns it.
   104  // The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
   105  //
   106  // Note that,
   107  // 1. It only retrieves the exported attributes with first letter upper-case from struct.
   108  // 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
   109  // 3. If one field has no specified tag, it uses its field name as result map key.
   110  func TagMapField(object interface{}, priority []string) (map[string]Field, error) {
   111  	fields, err := TagFields(object, priority)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	tagMap := make(map[string]Field, len(fields))
   116  	for _, field := range fields {
   117  		tagField := field
   118  		tagMap[field.TagValue] = tagField
   119  	}
   120  	return tagMap, nil
   121  }
   122  
   123  func getFieldValues(structObject interface{}) ([]Field, error) {
   124  	var (
   125  		reflectValue reflect.Value
   126  		reflectKind  reflect.Kind
   127  	)
   128  	if v, ok := structObject.(reflect.Value); ok {
   129  		reflectValue = v
   130  		reflectKind = reflectValue.Kind()
   131  	} else {
   132  		reflectValue = reflect.ValueOf(structObject)
   133  		reflectKind = reflectValue.Kind()
   134  	}
   135  	for {
   136  		switch reflectKind {
   137  		case reflect.Ptr:
   138  			if !reflectValue.IsValid() || reflectValue.IsNil() {
   139  				// If pointer is type of *struct and nil, then automatically create a temporary struct.
   140  				reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
   141  				reflectKind = reflectValue.Kind()
   142  			} else {
   143  				reflectValue = reflectValue.Elem()
   144  				reflectKind = reflectValue.Kind()
   145  			}
   146  		case reflect.Array, reflect.Slice:
   147  			reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
   148  			reflectKind = reflectValue.Kind()
   149  		default:
   150  			goto exitLoop
   151  		}
   152  	}
   153  
   154  exitLoop:
   155  	for reflectKind == reflect.Ptr {
   156  		reflectValue = reflectValue.Elem()
   157  		reflectKind = reflectValue.Kind()
   158  	}
   159  	if reflectKind != reflect.Struct {
   160  		return nil, gerror.NewCode(
   161  			gcode.CodeInvalidParameter,
   162  			"given value should be either type of struct/*struct/[]struct/[]*struct",
   163  		)
   164  	}
   165  	var (
   166  		structType = reflectValue.Type()
   167  		length     = reflectValue.NumField()
   168  		fields     = make([]Field, length)
   169  	)
   170  	for i := 0; i < length; i++ {
   171  		fields[i] = Field{
   172  			Value: reflectValue.Field(i),
   173  			Field: structType.Field(i),
   174  		}
   175  	}
   176  	return fields, nil
   177  }
   178  
   179  func getFieldValuesByTagPriority(
   180  	pointer interface{}, priority []string, repeatedTagFilteringMap map[string]struct{},
   181  ) ([]Field, error) {
   182  	fields, err := getFieldValues(pointer)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	var (
   187  		tagName   string
   188  		tagValue  string
   189  		tagFields = make([]Field, 0)
   190  	)
   191  	for _, field := range fields {
   192  		// Only retrieve exported attributes.
   193  		if !field.IsExported() {
   194  			continue
   195  		}
   196  		tagValue = ""
   197  		for _, p := range priority {
   198  			tagName = p
   199  			tagValue = field.Tag(p)
   200  			if tagValue != "" && tagValue != "-" {
   201  				break
   202  			}
   203  		}
   204  		if tagValue != "" {
   205  			// Filter repeated tag.
   206  			if _, ok := repeatedTagFilteringMap[tagValue]; ok {
   207  				continue
   208  			}
   209  			tagField := field
   210  			tagField.TagName = tagName
   211  			tagField.TagValue = tagValue
   212  			tagFields = append(tagFields, tagField)
   213  		}
   214  		// If this is an embedded attribute, it retrieves the tags recursively.
   215  		if field.IsEmbedded() && field.OriginalKind() == reflect.Struct {
   216  			subTagFields, err := getFieldValuesByTagPriority(field.Value, priority, repeatedTagFilteringMap)
   217  			if err != nil {
   218  				return nil, err
   219  			} else {
   220  				tagFields = append(tagFields, subTagFields...)
   221  			}
   222  		}
   223  	}
   224  	return tagFields, nil
   225  }