|
| 1 | +package httpheader |
| 2 | + |
| 3 | +import ( |
| 4 | +"bytes" |
| 5 | +"fmt" |
| 6 | +"net/http" |
| 7 | +"reflect" |
| 8 | +"strconv" |
| 9 | +"strings" |
| 10 | +"time" |
| 11 | +) |
| 12 | + |
| 13 | +const tagName = "header" |
| 14 | + |
| 15 | +// Version ... |
| 16 | +const Version = "0.1.0" |
| 17 | + |
| 18 | +var timeType = reflect.TypeOf(time.Time{}) |
| 19 | + |
| 20 | +var encoderType = reflect.TypeOf(new(Encoder)).Elem() |
| 21 | + |
| 22 | +// Encoder ... |
| 23 | +type Encoder interface { |
| 24 | +EncodeValues(key string, v *http.Header) error |
| 25 | +} |
| 26 | + |
| 27 | +// Header returns the http.Header encoding of v. |
| 28 | +func Header(v interface{}) (http.Header, error) { |
| 29 | +h := make(http.Header) |
| 30 | +val := reflect.ValueOf(v) |
| 31 | +for val.Kind() == reflect.Ptr { |
| 32 | +if val.IsNil() { |
| 33 | +return h, nil |
| 34 | +} |
| 35 | +val = val.Elem() |
| 36 | +} |
| 37 | + |
| 38 | +if v == nil { |
| 39 | +return h, nil |
| 40 | +} |
| 41 | + |
| 42 | +if val.Kind() != reflect.Struct { |
| 43 | +return nil, fmt.Errorf("httpheader: Header() expects struct input. Got %v", val.Kind()) |
| 44 | +} |
| 45 | + |
| 46 | +err := reflectValue(h, val) |
| 47 | +return h, err |
| 48 | +} |
| 49 | + |
| 50 | +// reflectValue populates the values parameter from the struct fields in val. |
| 51 | +// Embedded structs are followed recursively (using the rules defined in the |
| 52 | +// Values function documentation) breadth-first. |
| 53 | +func reflectValue(header http.Header, val reflect.Value) error { |
| 54 | +var embedded []reflect.Value |
| 55 | + |
| 56 | +typ := val.Type() |
| 57 | +for i := 0; i < typ.NumField(); i++ { |
| 58 | +sf := typ.Field(i) |
| 59 | +if sf.PkgPath != "" && !sf.Anonymous { // unexported |
| 60 | +continue |
| 61 | +} |
| 62 | + |
| 63 | +sv := val.Field(i) |
| 64 | +tag := sf.Tag.Get(tagName) |
| 65 | +if tag == "-" { |
| 66 | +continue |
| 67 | +} |
| 68 | +name, opts := parseTag(tag) |
| 69 | +if name == "" { |
| 70 | +if sf.Anonymous && sv.Kind() == reflect.Struct { |
| 71 | +// save embedded struct for later processing |
| 72 | +embedded = append(embedded, sv) |
| 73 | +continue |
| 74 | +} |
| 75 | + |
| 76 | +name = sf.Name |
| 77 | +} |
| 78 | + |
| 79 | +if opts.Contains("omitempty") && isEmptyValue(sv) { |
| 80 | +continue |
| 81 | +} |
| 82 | + |
| 83 | +if sv.Type().Implements(encoderType) { |
| 84 | +if !reflect.Indirect(sv).IsValid() { |
| 85 | +sv = reflect.New(sv.Type().Elem()) |
| 86 | +} |
| 87 | + |
| 88 | +m := sv.Interface().(Encoder) |
| 89 | +if err := m.EncodeValues(name, &header); err != nil { |
| 90 | +return err |
| 91 | +} |
| 92 | +continue |
| 93 | +} |
| 94 | + |
| 95 | +if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { |
| 96 | +var del byte |
| 97 | + |
| 98 | +if del != 0 { |
| 99 | +s := new(bytes.Buffer) |
| 100 | +first := true |
| 101 | +for i := 0; i < sv.Len(); i++ { |
| 102 | +if first { |
| 103 | +first = false |
| 104 | +} else { |
| 105 | +s.WriteByte(del) |
| 106 | +} |
| 107 | +s.WriteString(valueString(sv.Index(i), opts)) |
| 108 | +} |
| 109 | +header.Add(name, s.String()) |
| 110 | +} else { |
| 111 | +for i := 0; i < sv.Len(); i++ { |
| 112 | +k := name |
| 113 | +header.Add(k, valueString(sv.Index(i), opts)) |
| 114 | +} |
| 115 | +} |
| 116 | +continue |
| 117 | +} |
| 118 | + |
| 119 | +for sv.Kind() == reflect.Ptr { |
| 120 | +if sv.IsNil() { |
| 121 | +break |
| 122 | +} |
| 123 | +sv = sv.Elem() |
| 124 | +} |
| 125 | + |
| 126 | +if sv.Type() == timeType { |
| 127 | +header.Add(name, valueString(sv, opts)) |
| 128 | +continue |
| 129 | +} |
| 130 | + |
| 131 | +if sv.Kind() == reflect.Struct { |
| 132 | +reflectValue(header, sv) |
| 133 | +continue |
| 134 | +} |
| 135 | + |
| 136 | +header.Add(name, valueString(sv, opts)) |
| 137 | +} |
| 138 | + |
| 139 | +for _, f := range embedded { |
| 140 | +if err := reflectValue(header, f); err != nil { |
| 141 | +return err |
| 142 | +} |
| 143 | +} |
| 144 | + |
| 145 | +return nil |
| 146 | +} |
| 147 | + |
| 148 | +// valueString returns the string representation of a value. |
| 149 | +func valueString(v reflect.Value, opts tagOptions) string { |
| 150 | +for v.Kind() == reflect.Ptr { |
| 151 | +if v.IsNil() { |
| 152 | +return "" |
| 153 | +} |
| 154 | +v = v.Elem() |
| 155 | +} |
| 156 | + |
| 157 | +if v.Kind() == reflect.Bool && opts.Contains("int") { |
| 158 | +if v.Bool() { |
| 159 | +return "1" |
| 160 | +} |
| 161 | +return "0" |
| 162 | +} |
| 163 | + |
| 164 | +if v.Type() == timeType { |
| 165 | +t := v.Interface().(time.Time) |
| 166 | +if opts.Contains("unix") { |
| 167 | +return strconv.FormatInt(t.Unix(), 10) |
| 168 | +} |
| 169 | +return t.Format(http.TimeFormat) |
| 170 | +} |
| 171 | + |
| 172 | +return fmt.Sprint(v.Interface()) |
| 173 | +} |
| 174 | + |
| 175 | +// isEmptyValue checks if a value should be considered empty for the purposes |
| 176 | +// of omitting fields with the "omitempty" option. |
| 177 | +func isEmptyValue(v reflect.Value) bool { |
| 178 | +switch v.Kind() { |
| 179 | +case reflect.Array, reflect.Map, reflect.Slice, reflect.String: |
| 180 | +return v.Len() == 0 |
| 181 | +case reflect.Bool: |
| 182 | +return !v.Bool() |
| 183 | +case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| 184 | +return v.Int() == 0 |
| 185 | +case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| 186 | +return v.Uint() == 0 |
| 187 | +case reflect.Float32, reflect.Float64: |
| 188 | +return v.Float() == 0 |
| 189 | +case reflect.Interface, reflect.Ptr: |
| 190 | +return v.IsNil() |
| 191 | +} |
| 192 | + |
| 193 | +if v.Type() == timeType { |
| 194 | +return v.Interface().(time.Time).IsZero() |
| 195 | +} |
| 196 | + |
| 197 | +return false |
| 198 | +} |
| 199 | + |
| 200 | +// tagOptions is the string following a comma in a struct field's "url" tag, or |
| 201 | +// the empty string. It does not include the leading comma. |
| 202 | +type tagOptions []string |
| 203 | + |
| 204 | +// parseTag splits a struct field's url tag into its name and comma-separated |
| 205 | +// options. |
| 206 | +func parseTag(tag string) (string, tagOptions) { |
| 207 | +s := strings.Split(tag, ",") |
| 208 | +return s[0], s[1:] |
| 209 | +} |
| 210 | + |
| 211 | +// Contains checks whether the tagOptions contains the specified option. |
| 212 | +func (o tagOptions) Contains(option string) bool { |
| 213 | +for _, s := range o { |
| 214 | +if s == option { |
| 215 | +return true |
| 216 | +} |
| 217 | +} |
| 218 | +return false |
| 219 | +} |
0 commit comments