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