forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
func_result_dispatcher.go
230 lines (200 loc) · 6 KB
/
func_result_dispatcher.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package methodfunc
import (
"reflect"
"strings"
"github.com/kataras/iris/context"
)
// Result is a response dispatcher.
// All types that complete this interface
// can be returned as values from the method functions.
type Result interface {
// Dispatch should sends the response to the context's response writer.
Dispatch(ctx context.Context)
}
const slashB byte = '/'
type compatibleErr interface {
Error() string
}
// DefaultErrStatusCode is the default error status code (400)
// when the response contains an error which is not nil.
var DefaultErrStatusCode = 400
// DispatchErr writes the error to the response.
func DispatchErr(ctx context.Context, status int, err error) {
if status < 400 {
status = DefaultErrStatusCode
}
ctx.StatusCode(status)
if text := err.Error(); text != "" {
ctx.WriteString(text)
ctx.StopExecution()
}
}
// DispatchCommon is being used internally to send
// commonly used data to the response writer with a smart way.
func DispatchCommon(ctx context.Context,
statusCode int, contentType string, content []byte, v interface{}, err error, found bool) {
// if we have a false boolean as a return value
// then skip everything and fire a not found,
// we even don't care about the given status code or the object or the content.
if !found {
ctx.NotFound()
return
}
status := statusCode
if status == 0 {
status = 200
}
if err != nil {
DispatchErr(ctx, status, err)
return
}
// write the status code, the rest will need that before any write ofc.
ctx.StatusCode(status)
if contentType == "" {
// to respect any ctx.ContentType(...) call
// especially if v is not nil.
contentType = ctx.GetContentType()
}
if v != nil {
if d, ok := v.(Result); ok {
// write the content type now (internal check for empty value)
ctx.ContentType(contentType)
d.Dispatch(ctx)
return
}
if strings.HasPrefix(contentType, context.ContentJavascriptHeaderValue) {
_, err = ctx.JSONP(v)
} else if strings.HasPrefix(contentType, context.ContentXMLHeaderValue) {
_, err = ctx.XML(v, context.XML{Indent: " "})
} else {
// defaults to json if content type is missing or its application/json.
_, err = ctx.JSON(v, context.JSON{Indent: " "})
}
if err != nil {
DispatchErr(ctx, status, err)
}
return
}
ctx.ContentType(contentType)
// .Write even len(content) == 0 , this should be called in order to call the internal tryWriteHeader,
// it will not cost anything.
ctx.Write(content)
}
// DispatchFuncResult is being used internally to resolve
// and send the method function's output values to the
// context's response writer using a smart way which
// respects status code, content type, content, custom struct
// and an error type.
// Supports for:
// func(c *ExampleController) Get() string |
// (string, string) |
// (string, int) |
// ...
// int |
// (int, string |
// (string, error) |
// ...
// error |
// (int, error) |
// (customStruct, error) |
// ...
// bool |
// (int, bool) |
// (string, bool) |
// (customStruct, bool) |
// ...
// customStruct |
// (customStruct, int) |
// (customStruct, string) |
// Result or (Result, error) and so on...
//
// where Get is an HTTP METHOD.
func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
numOut := len(values)
if numOut == 0 {
return
}
var (
// if statusCode > 0 then send this status code.
// Except when err != nil then check if status code is < 400 and
// if it's set it as DefaultErrStatusCode.
// Except when found == false, then the status code is 404.
statusCode int
// if not empty then use that as content type,
// if empty and custom != nil then set it to application/json.
contentType string
// if len > 0 then write that to the response writer as raw bytes,
// except when found == false or err != nil or custom != nil.
content []byte
// if not nil then check
// for content type (or json default) and send the custom data object
// except when found == false or err != nil.
custom interface{}
// if not nil then check for its status code,
// if not status code or < 400 then set it as DefaultErrStatusCode
// and fire the error's text.
err error
// if false then skip everything and fire 404.
found = true // defaults to true of course, otherwise will break :)
)
for _, v := range values {
// order of these checks matters
// for example, first we need to check for status code,
// secondly the string (for content type and content)...
if !v.IsValid() {
continue
}
f := v.Interface()
if b, ok := f.(bool); ok {
found = b
if !found {
// skip everything, we don't care about other return values,
// this boolean is the higher in order.
break
}
continue
}
if i, ok := f.(int); ok {
statusCode = i
continue
}
if s, ok := f.(string); ok {
// a string is content type when it contains a slash and
// content or custom struct is being calculated already;
// (string -> content, string-> content type)
// (customStruct, string -> content type)
if (len(content) > 0 || custom != nil) && strings.IndexByte(s, slashB) > 0 {
contentType = s
} else {
// otherwise is content
content = []byte(s)
}
continue
}
if b, ok := f.([]byte); ok {
// it's raw content, get the latest
content = b
continue
}
if e, ok := f.(compatibleErr); ok {
if e != nil { // it's always not nil but keep it here.
err = e
if statusCode < 400 {
statusCode = DefaultErrStatusCode
}
break // break on first error, error should be in the end but we
// need to know break the dispatcher if any error.
// at the end; we don't want to write anything to the response if error is not nil.
}
continue
}
// else it's a custom struct or a dispatcher, we'll decide later
// because content type and status code matters
// do that check in order to be able to correctly dispatch:
// (customStruct, error) -> customStruct filled and error is nil
if custom == nil && f != nil {
custom = f
}
}
DispatchCommon(ctx, statusCode, contentType, content, custom, err, found)
}