2017-12-25 19:05:32 +01:00
|
|
|
package hero
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
|
2020-04-17 14:56:36 +02:00
|
|
|
"github.com/golang/protobuf/proto"
|
2019-10-25 00:27:02 +02:00
|
|
|
"github.com/kataras/iris/v12/context"
|
2017-12-25 19:05:32 +01:00
|
|
|
|
|
|
|
"github.com/fatih/structs"
|
|
|
|
)
|
|
|
|
|
2020-04-18 21:40:47 +02:00
|
|
|
// ResultHandler describes the function type which should serve the "v" struct value.
|
|
|
|
type ResultHandler func(ctx context.Context, v interface{}) error
|
|
|
|
|
|
|
|
func defaultResultHandler(ctx context.Context, v interface{}) error {
|
|
|
|
if p, ok := v.(PreflightResult); ok {
|
|
|
|
if err := p.Preflight(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if d, ok := v.(Result); ok {
|
|
|
|
d.Dispatch(ctx)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch context.TrimHeaderValue(ctx.GetContentType()) {
|
|
|
|
case context.ContentXMLHeaderValue, context.ContentXMLUnreadableHeaderValue:
|
|
|
|
_, err := ctx.XML(v)
|
|
|
|
return err
|
|
|
|
case context.ContentYAMLHeaderValue:
|
|
|
|
_, err := ctx.YAML(v)
|
|
|
|
return err
|
|
|
|
case context.ContentProtobufHeaderValue:
|
|
|
|
msg, ok := v.(proto.Message)
|
|
|
|
if !ok {
|
|
|
|
return context.ErrContentNotSupported
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := ctx.Protobuf(msg)
|
|
|
|
return err
|
|
|
|
case context.ContentMsgPackHeaderValue, context.ContentMsgPack2HeaderValue:
|
|
|
|
_, err := ctx.MsgPack(v)
|
|
|
|
return err
|
|
|
|
default:
|
|
|
|
// otherwise default to JSON.
|
|
|
|
_, err := ctx.JSON(v)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-25 19:05:32 +01:00
|
|
|
// Result is a response dispatcher.
|
|
|
|
// All types that complete this interface
|
|
|
|
// can be returned as values from the method functions.
|
|
|
|
//
|
2020-05-05 15:03:19 +02:00
|
|
|
// Example at: https://github.com/kataras/iris/tree/master/_examples/dependency-injection/overview.
|
2017-12-25 19:05:32 +01:00
|
|
|
type Result interface {
|
2020-03-02 09:07:44 +01:00
|
|
|
// Dispatch should send a response to the client.
|
|
|
|
Dispatch(context.Context)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PreflightResult is an interface which implementers
|
|
|
|
// should be responsible to perform preflight checks of a <T> resource (or Result) before sent to the client.
|
|
|
|
//
|
|
|
|
// If a non-nil error returned from the `Preflight` method then the JSON result
|
|
|
|
// will be not sent to the client and an ErrorHandler will be responsible to render the error.
|
|
|
|
//
|
|
|
|
// Usage: a custom struct value will be a JSON body response (by-default) but it contains
|
|
|
|
// "Code int" and `ID string` fields, the "Code" should be the status code of the response
|
|
|
|
// and the "ID" should be sent as a Header of "X-Request-ID: $ID".
|
|
|
|
//
|
|
|
|
// The caller can manage it at the handler itself. However,
|
|
|
|
// to reduce thoese type of duplications it's preferable to use such a standard interface instead.
|
|
|
|
type PreflightResult interface {
|
|
|
|
Preflight(context.Context) error
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var defaultFailureResponse = Response{Code: DefaultErrStatusCode}
|
|
|
|
|
|
|
|
// Try will check if "fn" ran without any panics,
|
|
|
|
// using recovery,
|
|
|
|
// and return its result as the final response
|
|
|
|
// otherwise it returns the "failure" response if any,
|
|
|
|
// if not then a 400 bad request is being sent.
|
|
|
|
//
|
|
|
|
// Example usage at: https://github.com/kataras/iris/blob/master/hero/func_result_test.go.
|
|
|
|
func Try(fn func() Result, failure ...Result) Result {
|
|
|
|
var failed bool
|
|
|
|
var actionResponse Result
|
|
|
|
|
|
|
|
func() {
|
|
|
|
defer func() {
|
|
|
|
if rec := recover(); rec != nil {
|
|
|
|
failed = true
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
actionResponse = fn()
|
|
|
|
}()
|
|
|
|
|
|
|
|
if failed {
|
|
|
|
if len(failure) > 0 {
|
|
|
|
return failure[0]
|
|
|
|
}
|
|
|
|
return defaultFailureResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
return actionResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
const slashB byte = '/'
|
|
|
|
|
|
|
|
type compatibleErr interface {
|
|
|
|
Error() string
|
|
|
|
}
|
|
|
|
|
2020-02-29 13:18:15 +01:00
|
|
|
// dispatchErr writes the error to the response.
|
|
|
|
func dispatchErr(ctx context.Context, status int, err error) bool {
|
|
|
|
if err == nil {
|
|
|
|
return false
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx.StatusCode(status)
|
2020-02-29 13:18:15 +01:00
|
|
|
DefaultErrorHandler.HandleError(ctx, err)
|
|
|
|
return true
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2020-04-18 21:40:47 +02:00
|
|
|
func dispatchFuncResult(ctx context.Context, values []reflect.Value, handler ResultHandler) error {
|
2017-12-25 19:05:32 +01:00
|
|
|
if len(values) == 0 {
|
2020-02-29 13:18:15 +01:00
|
|
|
return nil
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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 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() || !v.CanInterface() {
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
*/
|
|
|
|
switch value := f.(type) {
|
|
|
|
case bool:
|
|
|
|
found = value
|
|
|
|
if !found {
|
|
|
|
// skip everything, skip other values, we don't care about other return values,
|
|
|
|
// this boolean is the higher in order.
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case int:
|
|
|
|
statusCode = value
|
|
|
|
case string:
|
|
|
|
// 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(value, slashB) > 0 {
|
|
|
|
contentType = value
|
|
|
|
} else {
|
|
|
|
// otherwise is content
|
|
|
|
content = []byte(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
case []byte:
|
|
|
|
// it's raw content, get the latest
|
|
|
|
content = value
|
|
|
|
case compatibleErr:
|
2020-02-29 13:18:15 +01:00
|
|
|
if value == nil || isNil(v) {
|
2019-06-14 10:26:56 +02:00
|
|
|
continue
|
|
|
|
}
|
2019-04-16 17:01:48 +02:00
|
|
|
|
2020-03-01 01:17:19 +01:00
|
|
|
if statusCode < 400 && value != ErrStopExecution {
|
2019-06-14 10:26:56 +02:00
|
|
|
statusCode = DefaultErrStatusCode
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
2019-06-14 10:26:56 +02:00
|
|
|
|
2020-02-29 13:18:15 +01:00
|
|
|
ctx.StatusCode(statusCode)
|
|
|
|
return value
|
2017-12-25 19:05:32 +01:00
|
|
|
default:
|
|
|
|
// 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
|
2019-06-14 10:26:56 +02:00
|
|
|
if custom == nil {
|
|
|
|
// if it's a pointer to struct/map.
|
|
|
|
|
2020-02-29 13:18:15 +01:00
|
|
|
if isNil(v) {
|
2019-06-14 10:26:56 +02:00
|
|
|
// if just a ptr to struct with no content type given
|
|
|
|
// then try to get the previous response writer's content type,
|
|
|
|
// and if that is empty too then force-it to application/json
|
|
|
|
// as the default content type we use for structs/maps.
|
|
|
|
if contentType == "" {
|
2019-06-14 10:33:01 +02:00
|
|
|
contentType = ctx.GetContentType()
|
|
|
|
if contentType == "" {
|
|
|
|
contentType = context.ContentJSONHeaderValue
|
|
|
|
}
|
2019-06-14 10:26:56 +02:00
|
|
|
}
|
2019-06-14 10:33:01 +02:00
|
|
|
|
2019-06-14 10:26:56 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if value != nil {
|
|
|
|
custom = value // content type will be take care later on.
|
|
|
|
}
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-18 21:40:47 +02:00
|
|
|
return dispatchCommon(ctx, statusCode, contentType, content, custom, handler, found)
|
2020-02-29 13:18:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// dispatchCommon is being used internally to send
|
|
|
|
// commonly used data to the response writer with a smart way.
|
|
|
|
func dispatchCommon(ctx context.Context,
|
2020-04-18 21:40:47 +02:00
|
|
|
statusCode int, contentType string, content []byte, v interface{}, handler ResultHandler, found bool) error {
|
2020-02-29 13:18:15 +01:00
|
|
|
// 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 nil
|
|
|
|
}
|
|
|
|
|
|
|
|
status := statusCode
|
|
|
|
if status == 0 {
|
|
|
|
status = 200
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2020-03-01 01:17:19 +01:00
|
|
|
if contentType = ctx.GetContentType(); contentType == "" {
|
|
|
|
// 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,
|
|
|
|
// see dependency-injection/basic/middleware example)
|
|
|
|
contentType = context.ContentJSONHeaderValue
|
|
|
|
}
|
2020-02-29 13:18:15 +01:00
|
|
|
}
|
|
|
|
|
2020-04-18 21:40:47 +02:00
|
|
|
// write the content type now (internal check for empty value)
|
|
|
|
ctx.ContentType(contentType)
|
2020-02-29 13:18:15 +01:00
|
|
|
|
2020-04-18 21:40:47 +02:00
|
|
|
if v != nil {
|
|
|
|
return handler(ctx, v)
|
2020-02-29 13:18:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// .Write even len(content) == 0 , this should be called in order to call the internal tryWriteHeader,
|
|
|
|
// it will not cost anything.
|
|
|
|
_, err := ctx.Write(content)
|
|
|
|
return err
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Response completes the `methodfunc.Result` interface.
|
|
|
|
// It's being used as an alternative return value which
|
|
|
|
// wraps the status code, the content type, a content as bytes or as string
|
|
|
|
// and an error, it's smart enough to complete the request and send the correct response to the client.
|
|
|
|
type Response struct {
|
|
|
|
Code int
|
|
|
|
ContentType string
|
|
|
|
Content []byte
|
|
|
|
|
2020-05-06 05:36:05 +02:00
|
|
|
// If not empty then content type is the "text/plain"
|
|
|
|
// and content is the text as []byte. If not empty and
|
|
|
|
// the "Lang" field is not empty then this "Text" field
|
|
|
|
// becomes the current locale file's key.
|
2017-12-25 19:05:32 +01:00
|
|
|
Text string
|
2020-05-06 05:36:05 +02:00
|
|
|
// If not empty then "Text" field becomes the locale file's key that should point
|
|
|
|
// to a translation file's unique key. See `Object` for locale template data.
|
|
|
|
// The "Lang" field is the language code
|
|
|
|
// that should render the text inside the locale file's key.
|
|
|
|
Lang string
|
|
|
|
// If not nil then it will fire that as "application/json" or any
|
|
|
|
// previously set "ContentType". If "Lang" and "Text" are not empty
|
|
|
|
// then this "Object" field becomes the template data that the
|
|
|
|
// locale text should use to be rendered.
|
2017-12-25 19:05:32 +01:00
|
|
|
Object interface{}
|
|
|
|
|
|
|
|
// If Path is not empty then it will redirect
|
|
|
|
// the client to this Path, if Code is >= 300 and < 400
|
|
|
|
// then it will use that Code to do the redirection, otherwise
|
|
|
|
// StatusFound(302) or StatusSeeOther(303) for post methods will be used.
|
|
|
|
// Except when err != nil.
|
|
|
|
Path string
|
|
|
|
|
|
|
|
// if not empty then fire a 400 bad request error
|
|
|
|
// unless the Status is > 200, then fire that error code
|
|
|
|
// with the Err.Error() string as its content.
|
|
|
|
//
|
|
|
|
// if Err.Error() is empty then it fires the custom error handler
|
|
|
|
// if any otherwise the framework sends the default http error text based on the status.
|
|
|
|
Err error
|
|
|
|
Try func() int
|
|
|
|
|
|
|
|
// if true then it skips everything else and it throws a 404 not found error.
|
|
|
|
// Can be named as Failure but NotFound is more precise name in order
|
|
|
|
// to be visible that it's different than the `Err`
|
|
|
|
// because it throws a 404 not found instead of a 400 bad request.
|
|
|
|
// NotFound bool
|
|
|
|
// let's don't add this yet, it has its dangerous of missuse.
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ Result = Response{}
|
|
|
|
|
|
|
|
// Dispatch writes the response result to the context's response writer.
|
|
|
|
func (r Response) Dispatch(ctx context.Context) {
|
2020-05-06 05:36:05 +02:00
|
|
|
if dispatchErr(ctx, r.Code, r.Err) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Path != "" {
|
2017-12-25 19:05:32 +01:00
|
|
|
// it's not a redirect valid status
|
|
|
|
if r.Code < 300 || r.Code >= 400 {
|
|
|
|
if ctx.Method() == "POST" {
|
|
|
|
r.Code = 303 // StatusSeeOther
|
|
|
|
}
|
|
|
|
r.Code = 302 // StatusFound
|
|
|
|
}
|
|
|
|
ctx.Redirect(r.Path, r.Code)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-06 05:36:05 +02:00
|
|
|
if r.Text != "" {
|
|
|
|
if r.Lang != "" {
|
|
|
|
if r.Code > 0 {
|
|
|
|
ctx.StatusCode(r.Code)
|
|
|
|
}
|
|
|
|
ctx.ContentType(r.ContentType)
|
2017-12-25 19:05:32 +01:00
|
|
|
|
2020-05-06 05:36:05 +02:00
|
|
|
ctx.SetLanguage(r.Lang)
|
|
|
|
r.Content = []byte(ctx.Tr(r.Text, r.Object))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Content = []byte(r.Text)
|
2020-02-29 13:18:15 +01:00
|
|
|
}
|
|
|
|
|
2020-04-18 21:40:47 +02:00
|
|
|
err := dispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, defaultResultHandler, true)
|
2020-02-29 13:18:15 +01:00
|
|
|
dispatchErr(ctx, r.Code, err)
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// View completes the `hero.Result` interface.
|
|
|
|
// It's being used as an alternative return value which
|
|
|
|
// wraps the template file name, layout, (any) view data, status code and error.
|
|
|
|
// It's smart enough to complete the request and send the correct response to the client.
|
|
|
|
//
|
2020-05-05 15:03:19 +02:00
|
|
|
// Example at: https://github.com/kataras/iris/blob/master/_examples/dependency-injection/overview/web/routes/hello.go.
|
2017-12-25 19:05:32 +01:00
|
|
|
type View struct {
|
|
|
|
Name string
|
|
|
|
Layout string
|
|
|
|
Data interface{} // map or a custom struct.
|
|
|
|
Code int
|
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ Result = View{}
|
|
|
|
|
|
|
|
const dotB = byte('.')
|
|
|
|
|
|
|
|
// DefaultViewExt is the default extension if `view.Name `is missing,
|
|
|
|
// but note that it doesn't care about
|
|
|
|
// the app.RegisterView(iris.$VIEW_ENGINE("./$dir", "$ext"))'s $ext.
|
|
|
|
// so if you don't use the ".html" as extension for your files
|
|
|
|
// you have to append the extension manually into the `view.Name`
|
|
|
|
// or change this global variable.
|
|
|
|
var DefaultViewExt = ".html"
|
|
|
|
|
|
|
|
func ensureExt(s string) string {
|
|
|
|
if len(s) == 0 {
|
|
|
|
return "index" + DefaultViewExt
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.IndexByte(s, dotB) < 1 {
|
|
|
|
s += DefaultViewExt
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dispatch writes the template filename, template layout and (any) data to the client.
|
|
|
|
// Completes the `Result` interface.
|
|
|
|
func (r View) Dispatch(ctx context.Context) { // r as Response view.
|
2020-02-29 13:18:15 +01:00
|
|
|
if dispatchErr(ctx, r.Code, r.Err) {
|
2017-12-25 19:05:32 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Code > 0 {
|
|
|
|
ctx.StatusCode(r.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Name != "" {
|
|
|
|
r.Name = ensureExt(r.Name)
|
|
|
|
|
|
|
|
if r.Layout != "" {
|
|
|
|
r.Layout = ensureExt(r.Layout)
|
|
|
|
ctx.ViewLayout(r.Layout)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Data != nil {
|
|
|
|
// In order to respect any c.Ctx.ViewData that may called manually before;
|
|
|
|
dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
|
|
|
|
if ctx.Values().Get(dataKey) == nil {
|
|
|
|
// if no c.Ctx.ViewData set-ed before (the most common scenario) then do a
|
|
|
|
// simple set, it's faster.
|
|
|
|
ctx.Values().Set(dataKey, r.Data)
|
|
|
|
} else {
|
|
|
|
// else check if r.Data is map or struct, if struct convert it to map,
|
|
|
|
// do a range loop and modify the data one by one.
|
|
|
|
// context.Map is actually a map[string]interface{} but we have to make that check:
|
|
|
|
if m, ok := r.Data.(map[string]interface{}); ok {
|
|
|
|
setViewData(ctx, m)
|
|
|
|
} else if m, ok := r.Data.(context.Map); ok {
|
|
|
|
setViewData(ctx, m)
|
2020-02-29 13:18:15 +01:00
|
|
|
} else if reflect.Indirect(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
|
2017-12-25 19:05:32 +01:00
|
|
|
setViewData(ctx, structs.Map(r))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 15:29:06 +01:00
|
|
|
_ = ctx.View(r.Name)
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setViewData(ctx context.Context, data map[string]interface{}) {
|
|
|
|
for k, v := range data {
|
|
|
|
ctx.ViewData(k, v)
|
|
|
|
}
|
|
|
|
}
|