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 }