func_result.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. package hero
  2. import (
  3. "reflect"
  4. "strings"
  5. "github.com/kataras/iris/v12/context"
  6. "github.com/fatih/structs"
  7. "google.golang.org/protobuf/proto"
  8. )
  9. // ResultHandler describes the function type which should serve the "v" struct value.
  10. type ResultHandler func(ctx *context.Context, v interface{}) error
  11. func defaultResultHandler(ctx *context.Context, v interface{}) error {
  12. if p, ok := v.(PreflightResult); ok {
  13. if err := p.Preflight(ctx); err != nil {
  14. return err
  15. }
  16. }
  17. if d, ok := v.(Result); ok {
  18. d.Dispatch(ctx)
  19. return nil
  20. }
  21. switch context.TrimHeaderValue(ctx.GetContentType()) {
  22. case context.ContentXMLHeaderValue, context.ContentXMLUnreadableHeaderValue:
  23. return ctx.XML(v)
  24. case context.ContentYAMLHeaderValue:
  25. return ctx.YAML(v)
  26. case context.ContentProtobufHeaderValue:
  27. msg, ok := v.(proto.Message)
  28. if !ok {
  29. return context.ErrContentNotSupported
  30. }
  31. _, err := ctx.Protobuf(msg)
  32. return err
  33. case context.ContentMsgPackHeaderValue, context.ContentMsgPack2HeaderValue:
  34. _, err := ctx.MsgPack(v)
  35. return err
  36. default:
  37. // otherwise default to JSON.
  38. return ctx.JSON(v)
  39. }
  40. }
  41. // Result is a response dispatcher.
  42. // All types that complete this interface
  43. // can be returned as values from the method functions.
  44. //
  45. // Example at: https://github.com/kataras/iris/tree/main/_examples/dependency-injection/overview.
  46. type Result interface {
  47. // Dispatch should send a response to the client.
  48. Dispatch(*context.Context)
  49. }
  50. // PreflightResult is an interface which implementers
  51. // should be responsible to perform preflight checks of a <T> resource (or Result) before sent to the client.
  52. //
  53. // If a non-nil error returned from the `Preflight` method then the JSON result
  54. // will be not sent to the client and an ErrorHandler will be responsible to render the error.
  55. //
  56. // Usage: a custom struct value will be a JSON body response (by-default) but it contains
  57. // "Code int" and `ID string` fields, the "Code" should be the status code of the response
  58. // and the "ID" should be sent as a Header of "X-Request-ID: $ID".
  59. //
  60. // The caller can manage it at the handler itself. However,
  61. // to reduce thoese type of duplications it's preferable to use such a standard interface instead.
  62. //
  63. // The Preflight method can return `iris.ErrStopExecution` to render
  64. // and override any interface that the structure value may implement, e.g. mvc.Result.
  65. type PreflightResult interface {
  66. Preflight(*context.Context) error
  67. }
  68. var defaultFailureResponse = Response{Code: DefaultErrStatusCode}
  69. // Try will check if "fn" ran without any panics,
  70. // using recovery,
  71. // and return its result as the final response
  72. // otherwise it returns the "failure" response if any,
  73. // if not then a 400 bad request is being sent.
  74. //
  75. // Example usage at: https://github.com/kataras/iris/blob/main/hero/func_result_test.go.
  76. func Try(fn func() Result, failure ...Result) Result {
  77. var failed bool
  78. var actionResponse Result
  79. func() {
  80. defer func() {
  81. if rec := recover(); rec != nil {
  82. failed = true
  83. }
  84. }()
  85. actionResponse = fn()
  86. }()
  87. if failed {
  88. if len(failure) > 0 {
  89. return failure[0]
  90. }
  91. return defaultFailureResponse
  92. }
  93. return actionResponse
  94. }
  95. const slashB byte = '/'
  96. type compatibleErr interface {
  97. Error() string
  98. }
  99. // dispatchErr sets the error status code
  100. // and the error value to the context.
  101. // The APIBuilder's On(Any)ErrorCode is responsible to render this error code.
  102. func dispatchErr(ctx *context.Context, status int, err error) bool {
  103. if err == nil {
  104. return false
  105. }
  106. if err != ErrStopExecution {
  107. if status == 0 || !context.StatusCodeNotSuccessful(status) {
  108. status = DefaultErrStatusCode
  109. }
  110. ctx.StatusCode(status)
  111. }
  112. ctx.SetErr(err)
  113. return true
  114. }
  115. // DispatchFuncResult is being used internally to resolve
  116. // and send the method function's output values to the
  117. // context's response writer using a smart way which
  118. // respects status code, content type, content, custom struct
  119. // and an error type.
  120. // Supports for:
  121. // func(c *ExampleController) Get() string |
  122. // (string, string) |
  123. // (string, int) |
  124. // ...
  125. // int |
  126. // (int, string |
  127. // (string, error) |
  128. // ...
  129. // error |
  130. // (int, error) |
  131. // (customStruct, error) |
  132. // ...
  133. // bool |
  134. // (int, bool) |
  135. // (string, bool) |
  136. // (customStruct, bool) |
  137. // ...
  138. // customStruct |
  139. // (customStruct, int) |
  140. // (customStruct, string) |
  141. // Result or (Result, error) and so on...
  142. //
  143. // where Get is an HTTP METHOD.
  144. func dispatchFuncResult(ctx *context.Context, values []reflect.Value, handler ResultHandler) error {
  145. if len(values) == 0 {
  146. return nil
  147. }
  148. var (
  149. // if statusCode > 0 then send this status code.
  150. // Except when err != nil then check if status code is < 400 and
  151. // if it's set it as DefaultErrStatusCode.
  152. // Except when found == false, then the status code is 404.
  153. statusCode = ctx.GetStatusCode() // Get the current status code given by any previous middleware.
  154. // if not empty then use that as content type,
  155. // if empty and custom != nil then set it to application/json.
  156. contentType string
  157. // if len > 0 then write that to the response writer as raw bytes,
  158. // except when found == false or err != nil or custom != nil.
  159. content []byte
  160. // if not nil then check
  161. // for content type (or json default) and send the custom data object
  162. // except when found == false or err != nil.
  163. custom interface{}
  164. // if false then skip everything and fire 404.
  165. found = true // defaults to true of course, otherwise will break :)
  166. )
  167. for _, v := range values {
  168. // order of these checks matters
  169. // for example, first we need to check for status code,
  170. // secondly the string (for content type and content)...
  171. // if !v.IsValid() || !v.CanInterface() {
  172. // continue
  173. // }
  174. if !v.IsValid() {
  175. continue
  176. }
  177. f := v.Interface()
  178. /*
  179. if b, ok := f.(bool); ok {
  180. found = b
  181. if !found {
  182. // skip everything, we don't care about other return values,
  183. // this boolean is the higher in order.
  184. break
  185. }
  186. continue
  187. }
  188. if i, ok := f.(int); ok {
  189. statusCode = i
  190. continue
  191. }
  192. if s, ok := f.(string); ok {
  193. // a string is content type when it contains a slash and
  194. // content or custom struct is being calculated already;
  195. // (string -> content, string-> content type)
  196. // (customStruct, string -> content type)
  197. if (len(content) > 0 || custom != nil) && strings.IndexByte(s, slashB) > 0 {
  198. contentType = s
  199. } else {
  200. // otherwise is content
  201. content = []byte(s)
  202. }
  203. continue
  204. }
  205. if b, ok := f.([]byte); ok {
  206. // it's raw content, get the latest
  207. content = b
  208. continue
  209. }
  210. if e, ok := f.(compatibleErr); ok {
  211. if e != nil { // it's always not nil but keep it here.
  212. err = e
  213. if statusCode < 400 {
  214. statusCode = DefaultErrStatusCode
  215. }
  216. break // break on first error, error should be in the end but we
  217. // need to know break the dispatcher if any error.
  218. // at the end; we don't want to write anything to the response if error is not nil.
  219. }
  220. continue
  221. }
  222. // else it's a custom struct or a dispatcher, we'll decide later
  223. // because content type and status code matters
  224. // do that check in order to be able to correctly dispatch:
  225. // (customStruct, error) -> customStruct filled and error is nil
  226. if custom == nil && f != nil {
  227. custom = f
  228. }
  229. }
  230. */
  231. switch value := f.(type) {
  232. case bool:
  233. found = value
  234. if !found {
  235. // skip everything, skip other values, we don't care about other return values,
  236. // this boolean is the higher in order.
  237. break
  238. }
  239. case int:
  240. statusCode = value
  241. case string:
  242. // a string is content type when it contains a slash and
  243. // content or custom struct is being calculated already;
  244. // (string -> content, string-> content type)
  245. // (customStruct, string -> content type)
  246. if (len(content) > 0 || custom != nil) && strings.IndexByte(value, slashB) > 0 {
  247. contentType = value
  248. } else {
  249. // otherwise is content
  250. contentType = context.ContentTextHeaderValue
  251. content = []byte(value)
  252. }
  253. case []byte:
  254. // it's raw content, get the latest
  255. content = value
  256. case compatibleErr:
  257. if value == nil || isNil(v) {
  258. continue
  259. }
  260. if statusCode < 400 && value != ErrStopExecution {
  261. statusCode = DefaultErrStatusCode
  262. }
  263. ctx.StatusCode(statusCode)
  264. return value
  265. default:
  266. // else it's a custom struct or a dispatcher, we'll decide later
  267. // because content type and status code matters
  268. // do that check in order to be able to correctly dispatch:
  269. // (customStruct, error) -> customStruct filled and error is nil
  270. if custom == nil {
  271. // if it's a pointer to struct/map.
  272. if isNil(v) {
  273. // if just a ptr to struct with no content type given
  274. // then try to get the previous response writer's content type,
  275. // and if that is empty too then force-it to application/json
  276. // as the default content type we use for structs/maps.
  277. if contentType == "" {
  278. contentType = ctx.GetContentType()
  279. if contentType == "" {
  280. contentType = context.ContentJSONHeaderValue
  281. }
  282. }
  283. continue
  284. }
  285. if value != nil {
  286. custom = value // content type will be take care later on.
  287. }
  288. }
  289. }
  290. }
  291. return dispatchCommon(ctx, statusCode, contentType, content, custom, handler, found)
  292. }
  293. // dispatchCommon is being used internally to send
  294. // commonly used data to the response writer with a smart way.
  295. func dispatchCommon(ctx *context.Context,
  296. statusCode int, contentType string, content []byte, v interface{}, handler ResultHandler, found bool) error {
  297. // if we have a false boolean as a return value
  298. // then skip everything and fire a not found,
  299. // we even don't care about the given status code or the object or the content.
  300. if !found {
  301. ctx.NotFound()
  302. return nil
  303. }
  304. status := statusCode
  305. if status == 0 {
  306. status = 200
  307. }
  308. // write the status code, the rest will need that before any write ofc.
  309. ctx.StatusCode(status)
  310. if contentType == "" {
  311. // to respect any ctx.ContentType(...) call
  312. // especially if v is not nil.
  313. if contentType = ctx.GetContentType(); contentType == "" {
  314. // if it's still empty set to JSON. (useful for dynamic middlewares that returns an int status code and the next handler dispatches the JSON,
  315. // see dependency-injection/basic/middleware example)
  316. contentType = context.ContentJSONHeaderValue
  317. }
  318. }
  319. // write the content type now (internal check for empty value)
  320. ctx.ContentType(contentType)
  321. if v != nil {
  322. return handler(ctx, v)
  323. }
  324. // .Write even len(content) == 0 , this should be called in order to call the internal tryWriteHeader,
  325. // it will not cost anything.
  326. _, err := ctx.Write(content)
  327. return err
  328. }
  329. // Response completes the `methodfunc.Result` interface.
  330. // It's being used as an alternative return value which
  331. // wraps the status code, the content type, a content as bytes or as string
  332. // and an error, it's smart enough to complete the request and send the correct response to the client.
  333. type Response struct {
  334. Code int
  335. ContentType string
  336. Content []byte
  337. // If not empty then content type is the "text/plain"
  338. // and content is the text as []byte. If not empty and
  339. // the "Lang" field is not empty then this "Text" field
  340. // becomes the current locale file's key.
  341. Text string
  342. // If not empty then "Text" field becomes the locale file's key that should point
  343. // to a translation file's unique key. See `Object` for locale template data.
  344. // The "Lang" field is the language code
  345. // that should render the text inside the locale file's key.
  346. Lang string
  347. // If not nil then it will fire that as "application/json" or any
  348. // previously set "ContentType". If "Lang" and "Text" are not empty
  349. // then this "Object" field becomes the template data that the
  350. // locale text should use to be rendered.
  351. Object interface{}
  352. // If Path is not empty then it will redirect
  353. // the client to this Path, if Code is >= 300 and < 400
  354. // then it will use that Code to do the redirection, otherwise
  355. // StatusFound(302) or StatusSeeOther(303) for post methods will be used.
  356. // Except when err != nil.
  357. Path string
  358. // if not empty then fire a 400 bad request error
  359. // unless the Status is > 200, then fire that error code
  360. // with the Err.Error() string as its content.
  361. //
  362. // if Err.Error() is empty then it fires the custom error handler
  363. // if any otherwise the framework sends the default http error text based on the status.
  364. Err error
  365. Try func() int
  366. // if true then it skips everything else and it throws a 404 not found error.
  367. // Can be named as Failure but NotFound is more precise name in order
  368. // to be visible that it's different than the `Err`
  369. // because it throws a 404 not found instead of a 400 bad request.
  370. // NotFound bool
  371. // let's don't add this yet, it has its dangerous of missuse.
  372. }
  373. var _ Result = Response{}
  374. // Dispatch writes the response result to the context's response writer.
  375. func (r Response) Dispatch(ctx *context.Context) {
  376. if dispatchErr(ctx, r.Code, r.Err) {
  377. return
  378. }
  379. if r.Path != "" {
  380. // it's not a redirect valid status
  381. if r.Code < 300 || r.Code >= 400 {
  382. if ctx.Method() == "POST" {
  383. r.Code = 303 // StatusSeeOther
  384. }
  385. r.Code = 302 // StatusFound
  386. }
  387. ctx.Redirect(r.Path, r.Code)
  388. return
  389. }
  390. if r.Text != "" {
  391. if r.Lang != "" {
  392. if r.Code > 0 {
  393. ctx.StatusCode(r.Code)
  394. }
  395. ctx.ContentType(r.ContentType)
  396. ctx.SetLanguage(r.Lang)
  397. r.Content = []byte(ctx.Tr(r.Text, r.Object))
  398. } else {
  399. r.Content = []byte(r.Text)
  400. }
  401. }
  402. err := dispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, defaultResultHandler, true)
  403. dispatchErr(ctx, r.Code, err)
  404. }
  405. // View completes the `hero.Result` interface.
  406. // It's being used as an alternative return value which
  407. // wraps the template file name, layout, (any) view data, status code and error.
  408. // It's smart enough to complete the request and send the correct response to the client.
  409. //
  410. // Example at: https://github.com/kataras/iris/blob/main/_examples/dependency-injection/overview/web/routes/hello.go.
  411. type View struct {
  412. Name string
  413. Layout string
  414. Data interface{} // map or a custom struct.
  415. Code int
  416. Err error
  417. }
  418. var _ Result = View{}
  419. // Dispatch writes the template filename, template layout and (any) data to the client.
  420. // Completes the `Result` interface.
  421. func (r View) Dispatch(ctx *context.Context) { // r as Response view.
  422. if dispatchErr(ctx, r.Code, r.Err) {
  423. return
  424. }
  425. if r.Code > 0 {
  426. ctx.StatusCode(r.Code)
  427. }
  428. if r.Name != "" {
  429. if r.Layout != "" {
  430. ctx.ViewLayout(r.Layout)
  431. }
  432. if r.Data != nil {
  433. // In order to respect any c.Ctx.ViewData that may called manually before;
  434. dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
  435. if ctx.Values().Get(dataKey) == nil {
  436. // if no c.Ctx.ViewData set-ed before (the most common scenario) then do a
  437. // simple set, it's faster.
  438. ctx.Values().Set(dataKey, r.Data)
  439. } else {
  440. // else check if r.Data is map or struct, if struct convert it to map,
  441. // do a range loop and modify the data one by one.
  442. // context.Map is actually a map[string]interface{} but we have to make that check:
  443. if m, ok := r.Data.(context.Map); ok {
  444. setViewData(ctx, m)
  445. } else if reflect.Indirect(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
  446. setViewData(ctx, structs.Map(r))
  447. }
  448. }
  449. }
  450. _ = ctx.View(r.Name)
  451. }
  452. }
  453. func setViewData(ctx *context.Context, data map[string]interface{}) {
  454. for k, v := range data {
  455. ctx.ViewData(k, v)
  456. }
  457. }