-
Notifications
You must be signed in to change notification settings - Fork 62
/
render.go
143 lines (118 loc) · 2.95 KB
/
render.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package render
import (
"net/http"
"reflect"
)
// Renderer interface for managing response payloads.
type Renderer interface {
Render(w http.ResponseWriter, r *http.Request) error
}
// Binder interface for managing request payloads.
type Binder interface {
Bind(r *http.Request) error
}
// Bind decodes a request body and executes the Binder method of the
// payload structure.
func Bind(r *http.Request, v Binder) error {
if err := Decode(r, v); err != nil {
return err
}
return binder(r, v)
}
// Render renders a single payload and respond to the client request.
func Render(w http.ResponseWriter, r *http.Request, v Renderer) error {
if err := renderer(w, r, v); err != nil {
return err
}
Respond(w, r, v)
return nil
}
// RenderList renders a slice of payloads and responds to the client request.
func RenderList(w http.ResponseWriter, r *http.Request, l []Renderer) error {
for _, v := range l {
if err := renderer(w, r, v); err != nil {
return err
}
}
Respond(w, r, l)
return nil
}
func isNil(f reflect.Value) bool {
switch f.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return f.IsNil()
default:
return false
}
}
// Executed top-down
func renderer(w http.ResponseWriter, r *http.Request, v Renderer) error {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
// We call it top-down.
if err := v.Render(w, r); err != nil {
return err
}
// We're done if the Renderer isn't a struct object
if rv.Kind() != reflect.Struct {
return nil
}
// For structs, we call Render on each field that implements Renderer
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
if f.Type().Implements(rendererType) {
if isNil(f) {
continue
}
fv := f.Interface().(Renderer)
if err := renderer(w, r, fv); err != nil {
return err
}
}
}
return nil
}
// Executed bottom-up
func binder(r *http.Request, v Binder) error {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
// Call Binder on non-struct types right away
if rv.Kind() != reflect.Struct {
return v.Bind(r)
}
// For structs, we call Bind on each field that implements Binder
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
if f.Type().Implements(binderType) {
if isNil(f) {
continue
}
fv := f.Interface().(Binder)
if err := binder(r, fv); err != nil {
return err
}
}
}
// We call it bottom-up
if err := v.Bind(r); err != nil {
return err
}
return nil
}
var (
rendererType = reflect.TypeOf(new(Renderer)).Elem()
binderType = reflect.TypeOf(new(Binder)).Elem()
)
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation. This technique
// for defining context keys was copied from Go 1.7's new use of context in net/http.
type contextKey struct {
name string
}
func (k *contextKey) String() string {
return "chi render context value " + k.name
}