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