github.com/pubgo/xprocess@v0.1.11/xprocess_schema/encoder.go (about) 1 package xprocess_schema 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "strconv" 8 ) 9 10 type encoderFunc func(reflect.Value) string 11 12 // Encoder encodes values from a struct into url.Values. 13 type Encoder struct { 14 cache *cache 15 regenc map[reflect.Type]encoderFunc 16 } 17 18 // NewEncoder returns a new Encoder with defaults. 19 func NewEncoder() *Encoder { 20 return &Encoder{cache: newCache(), regenc: make(map[reflect.Type]encoderFunc)} 21 } 22 23 // Encode encodes a struct into map[string][]string. 24 // 25 // Intended for use with url.Values. 26 func (e *Encoder) Encode(src interface{}, dst map[string][]string) error { 27 v := reflect.ValueOf(src) 28 29 return e.encode(v, dst) 30 } 31 32 // RegisterEncoder registers a converter for encoding a custom type. 33 func (e *Encoder) RegisterEncoder(value interface{}, encoder func(reflect.Value) string) { 34 e.regenc[reflect.TypeOf(value)] = encoder 35 } 36 37 // SetAliasTag changes the tag used to locate custom field aliases. 38 // The default tag is "schema". 39 func (e *Encoder) SetAliasTag(tag string) { 40 e.cache.tag = tag 41 } 42 43 // isValidStructPointer test if input value is a valid struct pointer. 44 func isValidStructPointer(v reflect.Value) bool { 45 return v.Type().Kind() == reflect.Ptr && v.Elem().IsValid() && v.Elem().Type().Kind() == reflect.Struct 46 } 47 48 func isZero(v reflect.Value) bool { 49 switch v.Kind() { 50 case reflect.Func: 51 case reflect.Map, reflect.Slice: 52 return v.IsNil() || v.Len() == 0 53 case reflect.Array: 54 z := true 55 for i := 0; i < v.Len(); i++ { 56 z = z && isZero(v.Index(i)) 57 } 58 return z 59 case reflect.Struct: 60 type zero interface { 61 IsZero() bool 62 } 63 if v.Type().Implements(reflect.TypeOf((*zero)(nil)).Elem()) { 64 iz := v.MethodByName("IsZero").Call([]reflect.Value{})[0] 65 return iz.Interface().(bool) 66 } 67 z := true 68 for i := 0; i < v.NumField(); i++ { 69 z = z && isZero(v.Field(i)) 70 } 71 return z 72 } 73 // Compare other types directly: 74 z := reflect.Zero(v.Type()) 75 return v.Interface() == z.Interface() 76 } 77 78 func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { 79 if v.Kind() == reflect.Ptr { 80 v = v.Elem() 81 } 82 if v.Kind() != reflect.Struct { 83 return errors.New("schema: interface must be a struct") 84 } 85 t := v.Type() 86 87 errors := MultiError{} 88 89 for i := 0; i < v.NumField(); i++ { 90 name, opts := fieldAlias(t.Field(i), e.cache.tag) 91 if name == "-" { 92 continue 93 } 94 95 // Encode struct pointer types if the field is a valid pointer and a struct. 96 if isValidStructPointer(v.Field(i)) { 97 e.encode(v.Field(i).Elem(), dst) 98 continue 99 } 100 101 encFunc := typeEncoder(v.Field(i).Type(), e.regenc) 102 103 // Encode non-slice types and custom implementations immediately. 104 if encFunc != nil { 105 value := encFunc(v.Field(i)) 106 if opts.Contains("omitempty") && isZero(v.Field(i)) { 107 continue 108 } 109 110 dst[name] = append(dst[name], value) 111 continue 112 } 113 114 if v.Field(i).Type().Kind() == reflect.Struct { 115 e.encode(v.Field(i), dst) 116 continue 117 } 118 119 if v.Field(i).Type().Kind() == reflect.Slice { 120 encFunc = typeEncoder(v.Field(i).Type().Elem(), e.regenc) 121 } 122 123 if encFunc == nil { 124 errors[v.Field(i).Type().String()] = fmt.Errorf("schema: encoder not found for %v", v.Field(i)) 125 continue 126 } 127 128 // Encode a slice. 129 if v.Field(i).Len() == 0 && opts.Contains("omitempty") { 130 continue 131 } 132 133 dst[name] = []string{} 134 for j := 0; j < v.Field(i).Len(); j++ { 135 dst[name] = append(dst[name], encFunc(v.Field(i).Index(j))) 136 } 137 } 138 139 if len(errors) > 0 { 140 return errors 141 } 142 return nil 143 } 144 145 func typeEncoder(t reflect.Type, reg map[reflect.Type]encoderFunc) encoderFunc { 146 if f, ok := reg[t]; ok { 147 return f 148 } 149 150 switch t.Kind() { 151 case reflect.Bool: 152 return encodeBool 153 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 154 return encodeInt 155 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 156 return encodeUint 157 case reflect.Float32: 158 return encodeFloat32 159 case reflect.Float64: 160 return encodeFloat64 161 case reflect.Ptr: 162 f := typeEncoder(t.Elem(), reg) 163 return func(v reflect.Value) string { 164 if v.IsNil() { 165 return "null" 166 } 167 return f(v.Elem()) 168 } 169 case reflect.String: 170 return encodeString 171 default: 172 return nil 173 } 174 } 175 176 func encodeBool(v reflect.Value) string { 177 return strconv.FormatBool(v.Bool()) 178 } 179 180 func encodeInt(v reflect.Value) string { 181 return strconv.FormatInt(int64(v.Int()), 10) 182 } 183 184 func encodeUint(v reflect.Value) string { 185 return strconv.FormatUint(uint64(v.Uint()), 10) 186 } 187 188 func encodeFloat(v reflect.Value, bits int) string { 189 return strconv.FormatFloat(v.Float(), 'f', 6, bits) 190 } 191 192 func encodeFloat32(v reflect.Value) string { 193 return encodeFloat(v, 32) 194 } 195 196 func encodeFloat64(v reflect.Value) string { 197 return encodeFloat(v, 64) 198 } 199 200 func encodeString(v reflect.Value) string { 201 return v.String() 202 }