github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/valid/fields.go (about)

     1  package valid
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"unicode"
     8  )
     9  
    10  // NOTE: need to document that fieldName will be the JSON name - NOT the Go name, i.e. "customer_id" not "CustomerID"
    11  
    12  type ReadFielder interface {
    13  	ReadField(fieldName string) (interface{}, error)
    14  }
    15  
    16  // FIXME: should these really be public?  ReadField?  I can see it both ways but exposing this
    17  // and letting people write code that depends on it may be a mistake.
    18  
    19  func ReadField(obj interface{}, fieldName string) (interface{}, error) {
    20  
    21  	if obj == nil {
    22  		return nil, ErrNotFound
    23  	}
    24  
    25  	if rf, ok := obj.(ReadFielder); ok {
    26  		return rf.ReadField(fieldName)
    27  	}
    28  
    29  	objt := reflect.TypeOf(obj)
    30  	for objt.Kind() == reflect.Ptr {
    31  		objt = objt.Elem()
    32  	}
    33  	objv := reflect.ValueOf(obj)
    34  	for objv.Kind() == reflect.Ptr {
    35  		objv = objv.Elem()
    36  	}
    37  
    38  	if objt.Kind() == reflect.Map {
    39  		retval := objv.MapIndex(reflect.ValueOf(fieldName))
    40  		if !retval.IsValid() {
    41  			// for map we don't return ErrNotFound just nil
    42  			return nil, nil
    43  		}
    44  		return retval.Interface(), nil
    45  	}
    46  
    47  	if objt.Kind() == reflect.Struct {
    48  
    49  		sfi := StructFieldIndex(objt, fieldName)
    50  		if sfi < 0 {
    51  			return nil, ErrNotFound
    52  		}
    53  
    54  		sfv := objv.Field(sfi)
    55  		return sfv.Interface(), nil
    56  	}
    57  
    58  	return nil, fmt.Errorf("invalid type %T", obj)
    59  }
    60  
    61  type WriteFielder interface {
    62  	WriteField(fieldName string, v interface{}) error
    63  }
    64  
    65  func WriteField(obj interface{}, fieldName string, v interface{}) error {
    66  
    67  	if obj == nil {
    68  		return ErrNotFound
    69  	}
    70  
    71  	if wf, ok := obj.(WriteFielder); ok {
    72  		return wf.WriteField(fieldName, v)
    73  	}
    74  
    75  	objt := reflect.TypeOf(obj)
    76  	for objt.Kind() == reflect.Ptr {
    77  		objt = objt.Elem()
    78  	}
    79  	objv := reflect.ValueOf(obj)
    80  	for objv.Kind() == reflect.Ptr {
    81  		objv = objv.Elem()
    82  	}
    83  
    84  	if objt.Kind() == reflect.Map {
    85  		objv.SetMapIndex(reflect.ValueOf(fieldName), reflect.ValueOf(v))
    86  		return nil
    87  	}
    88  
    89  	if objt.Kind() == reflect.Struct {
    90  
    91  		sfi := StructFieldIndex(objt, fieldName)
    92  		if sfi < 0 {
    93  			return ErrNotFound
    94  		}
    95  
    96  		sfv := objv.Field(sfi)
    97  		sfv.Set(reflect.ValueOf(v))
    98  
    99  		return nil
   100  	}
   101  
   102  	return fmt.Errorf("invalid type %T", obj)
   103  }
   104  
   105  func StructFieldIndex(structType reflect.Type, fieldName string) int {
   106  
   107  	for structType.Kind() == reflect.Ptr {
   108  		structType = structType.Elem()
   109  	}
   110  
   111  	if fieldName == "" {
   112  		return -1
   113  	}
   114  
   115  	convFieldName := ToSnake(fieldName)
   116  
   117  	for i := 0; i < structType.NumField(); i++ {
   118  
   119  		sf := structType.Field(i)
   120  
   121  		// check valid struct tag name=
   122  		vals := StructTagToValues(sf.Tag.Get("valid"))
   123  		name := vals.Get("name")
   124  		if name == fieldName || name == convFieldName {
   125  			return i
   126  		}
   127  
   128  		// check json struct tag
   129  		jsonName := strings.SplitN(sf.Tag.Get("json"), ",", 2)[0]
   130  		if jsonName == fieldName || jsonName == convFieldName {
   131  			return i
   132  		}
   133  
   134  		// check db struct tag
   135  		dbName := strings.SplitN(sf.Tag.Get("db"), ",", 2)[0]
   136  		if dbName == fieldName || dbName == convFieldName {
   137  			return i
   138  		}
   139  
   140  		// check exact match to field name
   141  		if sf.Name == fieldName || sf.Name == convFieldName {
   142  			return i
   143  		}
   144  
   145  	}
   146  
   147  	return -1
   148  
   149  }
   150  
   151  // Courtesy of: https://gist.github.com/elwinar/14e1e897fdbe4d3432e1
   152  // ToSnake converts the given string to snake case following the Golang format:
   153  // acronyms are converted to lower-case and preceded by an underscore.
   154  func ToSnake(in string) string {
   155  	runes := []rune(in)
   156  	length := len(runes)
   157  
   158  	var out []rune
   159  	for i := 0; i < length; i++ {
   160  		if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
   161  			out = append(out, '_')
   162  		}
   163  		out = append(out, unicode.ToLower(runes[i]))
   164  	}
   165  
   166  	// log.Printf("ToSnake(%q) -> %q", in, string(out))
   167  	return string(out)
   168  }