func_result.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. package hero
  2. import (
  3. "reflect"
  4. "strings"
  5. "github.com/kataras/iris/context"
  6. "github.com/kataras/iris/hero/di"
  7. "github.com/fatih/structs"
  8. )
  9. // Result is a response dispatcher.
  10. // All types that complete this interface
  11. // can be returned as values from the method functions.
  12. //
  13. // Example at: https://github.com/kataras/iris/tree/master/_examples/hero/overview.
  14. type Result interface {
  15. // Dispatch should sends the response to the context's response writer.
  16. Dispatch(ctx context.Context)
  17. }
  18. var defaultFailureResponse = Response{Code: DefaultErrStatusCode}
  19. // Try will check if "fn" ran without any panics,
  20. // using recovery,
  21. // and return its result as the final response
  22. // otherwise it returns the "failure" response if any,
  23. // if not then a 400 bad request is being sent.
  24. //
  25. // Example usage at: https://github.com/kataras/iris/blob/master/hero/func_result_test.go.
  26. func Try(fn func() Result, failure ...Result) Result {
  27. var failed bool
  28. var actionResponse Result
  29. func() {
  30. defer func() {
  31. if rec := recover(); rec != nil {
  32. failed = true
  33. }
  34. }()
  35. actionResponse = fn()
  36. }()
  37. if failed {
  38. if len(failure) > 0 {
  39. return failure[0]
  40. }
  41. return defaultFailureResponse
  42. }
  43. return actionResponse
  44. }
  45. const slashB byte = '/'
  46. type compatibleErr interface {
  47. Error() string
  48. }
  49. // DefaultErrStatusCode is the default error status code (400)
  50. // when the response contains an error which is not nil.
  51. var DefaultErrStatusCode = 400
  52. // DispatchErr writes the error to the response.
  53. func DispatchErr(ctx context.Context, status int, err error) {
  54. if status < 400 {
  55. status = DefaultErrStatusCode
  56. }
  57. ctx.StatusCode(status)
  58. if text := err.Error(); text != "" {
  59. ctx.WriteString(text)
  60. ctx.StopExecution()
  61. }
  62. }
  63. // DispatchCommon is being used internally to send
  64. // commonly used data to the response writer with a smart way.
  65. func DispatchCommon(ctx context.Context,
  66. statusCode int, contentType string, content []byte, v interface{}, err error, found bool) {
  67. // if we have a false boolean as a return value
  68. // then skip everything and fire a not found,
  69. // we even don't care about the given status code or the object or the content.
  70. if !found {
  71. ctx.NotFound()
  72. return
  73. }
  74. status := statusCode
  75. if status == 0 {
  76. status = 200
  77. }
  78. if err != nil {
  79. DispatchErr(ctx, status, err)
  80. return
  81. }
  82. // write the status code, the rest will need that before any write ofc.
  83. ctx.StatusCode(status)
  84. if contentType == "" {
  85. // to respect any ctx.ContentType(...) call
  86. // especially if v is not nil.
  87. contentType = ctx.GetContentType()
  88. }
  89. if v != nil {
  90. if d, ok := v.(Result); ok {
  91. // write the content type now (internal check for empty value)
  92. ctx.ContentType(contentType)
  93. d.Dispatch(ctx)
  94. return
  95. }
  96. if strings.HasPrefix(contentType, context.ContentJavascriptHeaderValue) {
  97. _, err = ctx.JSONP(v)
  98. } else if strings.HasPrefix(contentType, context.ContentXMLHeaderValue) {
  99. _, err = ctx.XML(v, context.XML{Indent: " "})
  100. } else {
  101. // defaults to json if content type is missing or its application/json.
  102. _, err = ctx.JSON(v, context.JSON{Indent: " "})
  103. }
  104. if err != nil {
  105. DispatchErr(ctx, status, err)
  106. }
  107. return
  108. }
  109. ctx.ContentType(contentType)
  110. // .Write even len(content) == 0 , this should be called in order to call the internal tryWriteHeader,
  111. // it will not cost anything.
  112. ctx.Write(content)
  113. }
  114. // DispatchFuncResult is being used internally to resolve
  115. // and send the method function's output values to the
  116. // context's response writer using a smart way which
  117. // respects status code, content type, content, custom struct
  118. // and an error type.
  119. // Supports for:
  120. // func(c *ExampleController) Get() string |
  121. // (string, string) |
  122. // (string, int) |
  123. // ...
  124. // int |
  125. // (int, string |
  126. // (string, error) |
  127. // ...
  128. // error |
  129. // (int, error) |
  130. // (customStruct, error) |
  131. // ...
  132. // bool |
  133. // (int, bool) |
  134. // (string, bool) |
  135. // (customStruct, bool) |
  136. // ...
  137. // customStruct |
  138. // (customStruct, int) |
  139. // (customStruct, string) |
  140. // Result or (Result, error) and so on...
  141. //
  142. // where Get is an HTTP METHOD.
  143. func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
  144. if len(values) == 0 {
  145. return
  146. }
  147. var (
  148. // if statusCode > 0 then send this status code.
  149. // Except when err != nil then check if status code is < 400 and
  150. // if it's set it as DefaultErrStatusCode.
  151. // Except when found == false, then the status code is 404.
  152. statusCode int
  153. // if not empty then use that as content type,
  154. // if empty and custom != nil then set it to application/json.
  155. contentType string
  156. // if len > 0 then write that to the response writer as raw bytes,
  157. // except when found == false or err != nil or custom != nil.
  158. content []byte
  159. // if not nil then check
  160. // for content type (or json default) and send the custom data object
  161. // except when found == false or err != nil.
  162. custom interface{}
  163. // if not nil then check for its status code,
  164. // if not status code or < 400 then set it as DefaultErrStatusCode
  165. // and fire the error's text.
  166. err error
  167. // if false then skip everything and fire 404.
  168. found = true // defaults to true of course, otherwise will break :)
  169. )
  170. for _, v := range values {
  171. // order of these checks matters
  172. // for example, first we need to check for status code,
  173. // secondly the string (for content type and content)...
  174. // if !v.IsValid() || !v.CanInterface() {
  175. // continue
  176. // }
  177. if !v.IsValid() {
  178. continue
  179. }
  180. f := v.Interface()
  181. /*
  182. if b, ok := f.(bool); ok {
  183. found = b
  184. if !found {
  185. // skip everything, we don't care about other return values,
  186. // this boolean is the higher in order.
  187. break
  188. }
  189. continue
  190. }
  191. if i, ok := f.(int); ok {
  192. statusCode = i
  193. continue
  194. }
  195. if s, ok := f.(string); ok {
  196. // a string is content type when it contains a slash and
  197. // content or custom struct is being calculated already;
  198. // (string -> content, string-> content type)
  199. // (customStruct, string -> content type)
  200. if (len(content) > 0 || custom != nil) && strings.IndexByte(s, slashB) > 0 {
  201. contentType = s
  202. } else {
  203. // otherwise is content
  204. content = []byte(s)
  205. }
  206. continue
  207. }
  208. if b, ok := f.([]byte); ok {
  209. // it's raw content, get the latest
  210. content = b
  211. continue
  212. }
  213. if e, ok := f.(compatibleErr); ok {
  214. if e != nil { // it's always not nil but keep it here.
  215. err = e
  216. if statusCode < 400 {
  217. statusCode = DefaultErrStatusCode
  218. }
  219. break // break on first error, error should be in the end but we
  220. // need to know break the dispatcher if any error.
  221. // at the end; we don't want to write anything to the response if error is not nil.
  222. }
  223. continue
  224. }
  225. // else it's a custom struct or a dispatcher, we'll decide later
  226. // because content type and status code matters
  227. // do that check in order to be able to correctly dispatch:
  228. // (customStruct, error) -> customStruct filled and error is nil
  229. if custom == nil && f != nil {
  230. custom = f
  231. }
  232. }
  233. */
  234. switch value := f.(type) {
  235. case bool:
  236. found = value
  237. if !found {
  238. // skip everything, skip other values, we don't care about other return values,
  239. // this boolean is the higher in order.
  240. break
  241. }
  242. case int:
  243. statusCode = value
  244. case string:
  245. // a string is content type when it contains a slash and
  246. // content or custom struct is being calculated already;
  247. // (string -> content, string-> content type)
  248. // (customStruct, string -> content type)
  249. if (len(content) > 0 || custom != nil) && strings.IndexByte(value, slashB) > 0 {
  250. contentType = value
  251. } else {
  252. // otherwise is content
  253. content = []byte(value)
  254. }
  255. case []byte:
  256. // it's raw content, get the latest
  257. content = value
  258. case compatibleErr:
  259. if value != nil { // it's always not nil but keep it here.
  260. err = value
  261. if statusCode < 400 {
  262. statusCode = DefaultErrStatusCode
  263. }
  264. break // break on first error, error should be in the end but we
  265. // need to know break the dispatcher if any error.
  266. // at the end; we don't want to write anything to the response if error is not nil.
  267. }
  268. default:
  269. // else it's a custom struct or a dispatcher, we'll decide later
  270. // because content type and status code matters
  271. // do that check in order to be able to correctly dispatch:
  272. // (customStruct, error) -> customStruct filled and error is nil
  273. if custom == nil && f != nil {
  274. custom = f
  275. }
  276. }
  277. }
  278. DispatchCommon(ctx, statusCode, contentType, content, custom, err, found)
  279. }
  280. // Response completes the `methodfunc.Result` interface.
  281. // It's being used as an alternative return value which
  282. // wraps the status code, the content type, a content as bytes or as string
  283. // and an error, it's smart enough to complete the request and send the correct response to the client.
  284. type Response struct {
  285. Code int
  286. ContentType string
  287. Content []byte
  288. // if not empty then content type is the text/plain
  289. // and content is the text as []byte.
  290. Text string
  291. // If not nil then it will fire that as "application/json" or the
  292. // "ContentType" if not empty.
  293. Object interface{}
  294. // If Path is not empty then it will redirect
  295. // the client to this Path, if Code is >= 300 and < 400
  296. // then it will use that Code to do the redirection, otherwise
  297. // StatusFound(302) or StatusSeeOther(303) for post methods will be used.
  298. // Except when err != nil.
  299. Path string
  300. // if not empty then fire a 400 bad request error
  301. // unless the Status is > 200, then fire that error code
  302. // with the Err.Error() string as its content.
  303. //
  304. // if Err.Error() is empty then it fires the custom error handler
  305. // if any otherwise the framework sends the default http error text based on the status.
  306. Err error
  307. Try func() int
  308. // if true then it skips everything else and it throws a 404 not found error.
  309. // Can be named as Failure but NotFound is more precise name in order
  310. // to be visible that it's different than the `Err`
  311. // because it throws a 404 not found instead of a 400 bad request.
  312. // NotFound bool
  313. // let's don't add this yet, it has its dangerous of missuse.
  314. }
  315. var _ Result = Response{}
  316. // Dispatch writes the response result to the context's response writer.
  317. func (r Response) Dispatch(ctx context.Context) {
  318. if r.Path != "" && r.Err == nil {
  319. // it's not a redirect valid status
  320. if r.Code < 300 || r.Code >= 400 {
  321. if ctx.Method() == "POST" {
  322. r.Code = 303 // StatusSeeOther
  323. }
  324. r.Code = 302 // StatusFound
  325. }
  326. ctx.Redirect(r.Path, r.Code)
  327. return
  328. }
  329. if s := r.Text; s != "" {
  330. r.Content = []byte(s)
  331. }
  332. DispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, r.Err, true)
  333. }
  334. // View completes the `hero.Result` interface.
  335. // It's being used as an alternative return value which
  336. // wraps the template file name, layout, (any) view data, status code and error.
  337. // It's smart enough to complete the request and send the correct response to the client.
  338. //
  339. // Example at: https://github.com/kataras/iris/blob/master/_examples/hero/overview/web/controllers/hello_controller.go.
  340. type View struct {
  341. Name string
  342. Layout string
  343. Data interface{} // map or a custom struct.
  344. Code int
  345. Err error
  346. }
  347. var _ Result = View{}
  348. const dotB = byte('.')
  349. // DefaultViewExt is the default extension if `view.Name `is missing,
  350. // but note that it doesn't care about
  351. // the app.RegisterView(iris.$VIEW_ENGINE("./$dir", "$ext"))'s $ext.
  352. // so if you don't use the ".html" as extension for your files
  353. // you have to append the extension manually into the `view.Name`
  354. // or change this global variable.
  355. var DefaultViewExt = ".html"
  356. func ensureExt(s string) string {
  357. if len(s) == 0 {
  358. return "index" + DefaultViewExt
  359. }
  360. if strings.IndexByte(s, dotB) < 1 {
  361. s += DefaultViewExt
  362. }
  363. return s
  364. }
  365. // Dispatch writes the template filename, template layout and (any) data to the client.
  366. // Completes the `Result` interface.
  367. func (r View) Dispatch(ctx context.Context) { // r as Response view.
  368. if r.Err != nil {
  369. if r.Code < 400 {
  370. r.Code = DefaultErrStatusCode
  371. }
  372. ctx.StatusCode(r.Code)
  373. ctx.WriteString(r.Err.Error())
  374. ctx.StopExecution()
  375. return
  376. }
  377. if r.Code > 0 {
  378. ctx.StatusCode(r.Code)
  379. }
  380. if r.Name != "" {
  381. r.Name = ensureExt(r.Name)
  382. if r.Layout != "" {
  383. r.Layout = ensureExt(r.Layout)
  384. ctx.ViewLayout(r.Layout)
  385. }
  386. if r.Data != nil {
  387. // In order to respect any c.Ctx.ViewData that may called manually before;
  388. dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
  389. if ctx.Values().Get(dataKey) == nil {
  390. // if no c.Ctx.ViewData set-ed before (the most common scenario) then do a
  391. // simple set, it's faster.
  392. ctx.Values().Set(dataKey, r.Data)
  393. } else {
  394. // else check if r.Data is map or struct, if struct convert it to map,
  395. // do a range loop and modify the data one by one.
  396. // context.Map is actually a map[string]interface{} but we have to make that check:
  397. if m, ok := r.Data.(map[string]interface{}); ok {
  398. setViewData(ctx, m)
  399. } else if m, ok := r.Data.(context.Map); ok {
  400. setViewData(ctx, m)
  401. } else if di.IndirectValue(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
  402. setViewData(ctx, structs.Map(r))
  403. }
  404. }
  405. }
  406. ctx.View(r.Name)
  407. }
  408. }
  409. func setViewData(ctx context.Context, data map[string]interface{}) {
  410. for k, v := range data {
  411. ctx.ViewData(k, v)
  412. }
  413. }