Skip to content

Commit 5bb0c20

Browse files
committed
first import
0 parents commit 5bb0c20

File tree

8 files changed

+342
-0
lines changed

8 files changed

+342
-0
lines changed

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Compiled Object files, Static and Dynamic libs (Shared Objects)
2+
*.o
3+
*.a
4+
*.so
5+
6+
# Folders
7+
_obj
8+
_test
9+
10+
# Architecture specific extensions/prefixes
11+
*.[568vq]
12+
[568vq].out
13+
14+
*.cgo1.go
15+
*.cgo2.c
16+
_cgo_defun.c
17+
_cgo_gotypes.go
18+
_cgo_export.*
19+
20+
_testmain.go
21+
22+
*.exe
23+
*.test
24+
*.prof
25+
dist/

.travis.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
language: go
2+
go:
3+
- 1.8
4+
5+
sudo: false
6+
7+
install:
8+
- go get .

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## 0.1.0 (2017-06-10)
4+
5+
* Initial Release

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 mozillazg
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
help:
2+
@echo "lint run lint"
3+
4+
.PHONY: lint
5+
lint:
6+
gofmt -s -w .
7+
golint .
8+
go vet

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# go-httpheader
2+
3+
go-httpheader is Go library for encoding structs into Header fields.
4+
5+
[![Build Status](https://circleci.com/bb/mozillazg/go-httpheader.svg?style=svg)](https://circleci.com/bb/mozillazg/go-httpheader)
6+
[![Go Report Card](https://goreportcard.com/badge/bitbucket.org/mozillazg/go-httpheader)](https://goreportcard.com/report/bitbucket.org/mozillazg/go-httpheader)
7+
8+
## install
9+
10+
`go get -u bitbucket.org/mozillazg/go-httpheader`
11+
12+
13+
## usage
14+
15+
See [example_test.go](./example_test.go).

encode.go

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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+
}

example_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package httpheader_test
2+
3+
import (
4+
"bitbucket.org/mozillazg/go-httpheader"
5+
"fmt"
6+
)
7+
8+
func ExampleHeader() {
9+
type Options struct {
10+
ContentType string `header:"Content-Type"`
11+
Length int `header:"Length"`
12+
XArray []string `header:"X-Array"`
13+
TestHide string `header:"-"`
14+
IgnoreEmpty string `header:"X-Empty,omitempty"`
15+
IgnoreEmptyN string `header:"X-Empty-N,omitempty"`
16+
}
17+
18+
opt := Options{
19+
ContentType: "application/json",
20+
Length: 2,
21+
XArray: []string{"test1", "test2"},
22+
TestHide: "hide",
23+
IgnoreEmptyN: "n",
24+
}
25+
h, _ := httpheader.Header(opt)
26+
fmt.Println(h["Content-Type"])
27+
fmt.Println(h["Length"])
28+
fmt.Println(h["X-Array"])
29+
_, ok := h["TestHide"]
30+
fmt.Println(ok)
31+
_, ok = h["X-Empty"]
32+
fmt.Println(ok)
33+
fmt.Println(h["X-Empty-N"])
34+
// Output:
35+
// [application/json]
36+
// [2]
37+
// [test1 test2]
38+
// false
39+
// false
40+
// [n]
41+
}

0 commit comments

Comments
 (0)