mirror of
https://github.com/kataras/iris.git
synced 2026-01-20 02:16:08 +00:00
Update to version 8.5.0 | NEW: MVC Output Result | Read HISTORY.md
Former-commit-id: 6a3579f2500fc715d7dc606478960946dcade61d
This commit is contained in:
@@ -131,9 +131,15 @@ func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler
|
||||
t.persistenceController.Handle(c)
|
||||
}
|
||||
|
||||
// if previous (binded) handlers stoped the execution
|
||||
// we should know that.
|
||||
if ctx.IsStopped() {
|
||||
return
|
||||
}
|
||||
|
||||
// init the request.
|
||||
b.BeginRequest(ctx)
|
||||
if ctx.IsStopped() {
|
||||
if ctx.IsStopped() { // if begin request stopped the execution
|
||||
return
|
||||
}
|
||||
|
||||
@@ -146,7 +152,8 @@ func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler
|
||||
t.modelController.Handle(ctx, c)
|
||||
}
|
||||
|
||||
// finally, execute the controller, don't check for IsStopped.
|
||||
// end the request, don't check for stopped because this does the actual writing
|
||||
// if no response written already.
|
||||
b.EndRequest(ctx)
|
||||
}
|
||||
}
|
||||
@@ -170,8 +177,11 @@ func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) {
|
||||
}
|
||||
// the actual method functions
|
||||
// i.e for "GET" it's the `Get()`.
|
||||
methods := methodfunc.Resolve(t.Type)
|
||||
|
||||
methods, err := methodfunc.Resolve(t.Type)
|
||||
if err != nil {
|
||||
golog.Errorf("MVC %s: %s", t.FullName, err.Error())
|
||||
// don't stop here.
|
||||
}
|
||||
// range over the type info's method funcs,
|
||||
// build a new handler for each of these
|
||||
// methods and register them to their
|
||||
@@ -181,7 +191,7 @@ func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) {
|
||||
for _, m := range methods {
|
||||
h := t.HandlerOf(m)
|
||||
if h == nil {
|
||||
golog.Debugf("MVC %s: nil method handler found for %s", t.FullName, m.Name)
|
||||
golog.Warnf("MVC %s: nil method handler found for %s", t.FullName, m.Name)
|
||||
continue
|
||||
}
|
||||
registeredHandlers := append(middleware, h)
|
||||
|
||||
@@ -173,7 +173,6 @@ func LookupFields(elem reflect.Type, matcher func(reflect.StructField) bool, han
|
||||
// is easier for debugging, if ever needed.
|
||||
func lookupStructField(elem reflect.Type, matcher func(reflect.StructField) bool, handler func(*Field)) *Field {
|
||||
// fmt.Printf("lookup struct for elem: %s\n", elem.Name())
|
||||
|
||||
// ignore if that field is not a struct
|
||||
if elem.Kind() != reflect.Struct {
|
||||
return nil
|
||||
@@ -182,6 +181,7 @@ func lookupStructField(elem reflect.Type, matcher func(reflect.StructField) bool
|
||||
// search by fields.
|
||||
for i, n := 0, elem.NumField(); i < n; i++ {
|
||||
elemField := elem.Field(i)
|
||||
|
||||
if matcher(elemField) {
|
||||
// we area inside the correct type.
|
||||
f := &Field{
|
||||
|
||||
@@ -11,52 +11,9 @@ import (
|
||||
// to support more than one input arguments without performance cost compared to previous implementation.
|
||||
// so it's hard-coded written to check the length of input args and their types.
|
||||
func buildMethodCall(a *ast) func(ctx context.Context, f reflect.Value) {
|
||||
// if accepts one or more parameters.
|
||||
if a.dynamic {
|
||||
// if one function input argument then call the function
|
||||
// by "casting" (faster).
|
||||
if l := len(a.paramKeys); l == 1 {
|
||||
paramType := a.paramTypes[0]
|
||||
paramKey := a.paramKeys[0]
|
||||
|
||||
if paramType == paramTypeInt {
|
||||
return func(ctx context.Context, f reflect.Value) {
|
||||
v, _ := ctx.Params().GetInt(paramKey)
|
||||
f.Interface().(func(int))(v)
|
||||
}
|
||||
}
|
||||
|
||||
if paramType == paramTypeLong {
|
||||
return func(ctx context.Context, f reflect.Value) {
|
||||
v, _ := ctx.Params().GetInt64(paramKey)
|
||||
f.Interface().(func(int64))(v)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if paramType == paramTypeBoolean {
|
||||
return func(ctx context.Context, f reflect.Value) {
|
||||
v, _ := ctx.Params().GetBool(paramKey)
|
||||
f.Interface().(func(bool))(v)
|
||||
}
|
||||
}
|
||||
|
||||
// string, path...
|
||||
return func(ctx context.Context, f reflect.Value) {
|
||||
f.Interface().(func(string))(ctx.Params().Get(paramKey))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if func input arguments are more than one then
|
||||
// use the Call method (slower).
|
||||
return func(ctx context.Context, f reflect.Value) {
|
||||
f.Call(a.paramValues(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
// if it's static without any receivers then just call it.
|
||||
// if func input arguments are more than one then
|
||||
// use the Call method (slower).
|
||||
return func(ctx context.Context, f reflect.Value) {
|
||||
f.Interface().(func())()
|
||||
DispatchFuncResult(ctx, f.Call(a.paramValues(ctx)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ func fetchInfos(typ reflect.Type) (methods []FuncInfo) {
|
||||
// search the entire controller
|
||||
// for any compatible method function
|
||||
// and add that.
|
||||
|
||||
for i, n := 0, typ.NumMethod(); i < n; i++ {
|
||||
m := typ.Method(i)
|
||||
name := m.Name
|
||||
|
||||
@@ -87,6 +87,22 @@ func (p *funcParser) parse() (*ast, error) {
|
||||
|
||||
a.relPath += "/" + strings.ToLower(w)
|
||||
}
|
||||
|
||||
// This fixes a problem when developer misses to append the keyword `By`
|
||||
// to the method function when input arguments are declared (for path parameters binding).
|
||||
// We could just use it with `By` keyword but this is not a good practise
|
||||
// because what happens if we will become naive and declare something like
|
||||
// Get(id int) and GetBy(username string) or GetBy(id int) ? it's not working because of duplication of the path.
|
||||
// Docs are clear about that but we are humans, they may do a mistake by accident but
|
||||
// framework will not allow that.
|
||||
// So the best thing we can do to help prevent those errors is by printing that message
|
||||
// below to the developer.
|
||||
// Note: it should be at the end of the words loop because a.dynamic may be true later on.
|
||||
if numIn := p.info.Type.NumIn(); numIn > 1 && !a.dynamic {
|
||||
return nil, fmt.Errorf("found %d input arguments but keyword 'By' is missing from '%s'",
|
||||
// -1 because end-developer wants to know the actual input arguments, without the struct holder.
|
||||
numIn-1, p.info.Name)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
@@ -160,6 +176,10 @@ type ast struct {
|
||||
// }
|
||||
|
||||
func (a *ast) paramValues(ctx context.Context) []reflect.Value {
|
||||
if !a.dynamic {
|
||||
return nil
|
||||
}
|
||||
|
||||
l := len(a.paramKeys)
|
||||
values := make([]reflect.Value, l, l)
|
||||
for i := 0; i < l; i++ {
|
||||
|
||||
187
mvc/activator/methodfunc/func_result_dispatcher.go
Normal file
187
mvc/activator/methodfunc/func_result_dispatcher.go
Normal file
@@ -0,0 +1,187 @@
|
||||
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) {
|
||||
|
||||
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) |
|
||||
// customStruct |
|
||||
// (customStruct, int) |
|
||||
// (customStruct, string) |
|
||||
// Result or (Result, error)
|
||||
// where Get is an HTTP METHOD.
|
||||
func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
|
||||
numOut := len(values)
|
||||
if numOut == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
statusCode int
|
||||
contentType string
|
||||
content []byte
|
||||
custom interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
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 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)
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package methodfunc
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
// MethodFunc the handler function.
|
||||
@@ -26,15 +26,17 @@ type MethodFunc struct {
|
||||
// Resolve returns all the method funcs
|
||||
// necessary information and actions to
|
||||
// perform the request.
|
||||
func Resolve(typ reflect.Type) (methodFuncs []MethodFunc) {
|
||||
func Resolve(typ reflect.Type) ([]MethodFunc, error) {
|
||||
r := errors.NewReporter()
|
||||
var methodFuncs []MethodFunc
|
||||
infos := fetchInfos(typ)
|
||||
for _, info := range infos {
|
||||
parser := newFuncParser(info)
|
||||
a, err := parser.parse()
|
||||
if err != nil {
|
||||
golog.Errorf("MVC: %s\n", err)
|
||||
if r.AddErr(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
methodFunc := MethodFunc{
|
||||
RelPath: a.relPath,
|
||||
FuncInfo: info,
|
||||
@@ -44,5 +46,5 @@ func Resolve(typ reflect.Type) (methodFuncs []MethodFunc) {
|
||||
methodFuncs = append(methodFuncs, methodFunc)
|
||||
}
|
||||
|
||||
return
|
||||
return methodFuncs, r.Return()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,56 @@ import (
|
||||
"github.com/kataras/iris/mvc/activator"
|
||||
)
|
||||
|
||||
// C is the lightweight BaseController type as an alternative of the `Controller` struct type.
|
||||
// It contains only the Name of the controller and the Context, it's the best option
|
||||
// to balance the performance cost reflection uses
|
||||
// if your controller uses the new func output values dispatcher feature;
|
||||
// func(c *ExampleController) Get() string |
|
||||
// (string, string) |
|
||||
// (string, int) |
|
||||
// int |
|
||||
// (int, string |
|
||||
// (string, error) |
|
||||
// error |
|
||||
// (int, error) |
|
||||
// (customStruct, error) |
|
||||
// customStruct |
|
||||
// (customStruct, int) |
|
||||
// (customStruct, string) |
|
||||
// Result or (Result, error)
|
||||
// where Get is an HTTP Method func.
|
||||
//
|
||||
// Look `core/router#APIBuilder#Controller` method too.
|
||||
//
|
||||
// It completes the `activator.BaseController` interface.
|
||||
//
|
||||
// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/using-method-result/controllers.
|
||||
// Example usage at: https://github.com/kataras/iris/blob/master/mvc/method_result_test.go#L17.
|
||||
type C struct {
|
||||
// The Name of the `C` controller.
|
||||
Name string
|
||||
// The current context.Context.
|
||||
//
|
||||
// we have to name it for two reasons:
|
||||
// 1: can't ignore these via reflection, it doesn't give an option to
|
||||
// see if the functions is derived from another type.
|
||||
// 2: end-developer may want to use some method functions
|
||||
// or any fields that could be conflict with the context's.
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
var _ activator.BaseController = &C{}
|
||||
|
||||
// SetName sets the controller's full name.
|
||||
// It's called internally.
|
||||
func (c *C) SetName(name string) { c.Name = name }
|
||||
|
||||
// BeginRequest starts the request by initializing the `Context` field.
|
||||
func (c *C) BeginRequest(ctx context.Context) { c.Ctx = ctx }
|
||||
|
||||
// EndRequest does nothing, is here to complete the `BaseController` interface.
|
||||
func (c *C) EndRequest(ctx context.Context) {}
|
||||
|
||||
// Controller is the base controller for the high level controllers instances.
|
||||
//
|
||||
// This base controller is used as an alternative way of building
|
||||
@@ -61,6 +111,8 @@ import (
|
||||
// Note: Binded values of context.Handler type are being recognised as middlewares by the router.
|
||||
//
|
||||
// Look `core/router/APIBuilder#Controller` method too.
|
||||
//
|
||||
// It completes the `activator.BaseController` interface.
|
||||
type Controller struct {
|
||||
// Name contains the current controller's full name.
|
||||
//
|
||||
@@ -121,6 +173,10 @@ type Controller struct {
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
var _ activator.BaseController = &Controller{}
|
||||
|
||||
var ctrlSuffix = reflect.TypeOf(Controller{}).Name()
|
||||
|
||||
// SetName sets the controller's full name.
|
||||
// It's called internally.
|
||||
func (c *Controller) SetName(name string) {
|
||||
@@ -238,6 +294,7 @@ func (c *Controller) BeginRequest(ctx context.Context) {
|
||||
c.Params = ctx.Params()
|
||||
// response status code
|
||||
c.Status = ctx.GetStatusCode()
|
||||
|
||||
// share values
|
||||
c.Values = ctx.Values()
|
||||
// view data for templates, remember
|
||||
@@ -251,12 +308,12 @@ func (c *Controller) BeginRequest(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (c *Controller) tryWriteHeaders() {
|
||||
if status := c.Status; status > 0 && status != c.Ctx.GetStatusCode() {
|
||||
c.Ctx.StatusCode(status)
|
||||
if c.Status > 0 && c.Status != c.Ctx.GetStatusCode() {
|
||||
c.Ctx.StatusCode(c.Status)
|
||||
}
|
||||
|
||||
if contentType := c.ContentType; contentType != "" {
|
||||
c.Ctx.ContentType(contentType)
|
||||
if c.ContentType != "" {
|
||||
c.Ctx.ContentType(c.ContentType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +326,7 @@ func (c *Controller) tryWriteHeaders() {
|
||||
// It's called internally.
|
||||
// End-Developer can ovveride it but still should be called at the end.
|
||||
func (c *Controller) EndRequest(ctx context.Context) {
|
||||
if ctx.ResponseWriter().Written() > 0 {
|
||||
if ctx.ResponseWriter().Written() >= 0 { // status code only (0) or actual body written(>0)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -289,16 +346,10 @@ func (c *Controller) EndRequest(ctx context.Context) {
|
||||
if layout := c.Layout; layout != "" {
|
||||
ctx.ViewLayout(layout)
|
||||
}
|
||||
if data := c.Data; len(data) > 0 {
|
||||
for k, v := range data {
|
||||
ctx.ViewData(k, v)
|
||||
}
|
||||
if len(c.Data) > 0 {
|
||||
ctx.Values().Set(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey(), c.Data)
|
||||
}
|
||||
|
||||
ctx.View(view)
|
||||
}
|
||||
}
|
||||
|
||||
var ctrlSuffix = reflect.TypeOf(Controller{}).Name()
|
||||
|
||||
var _ activator.BaseController = &Controller{}
|
||||
|
||||
@@ -71,13 +71,13 @@ func TestControllerMethodFuncs(t *testing.T) {
|
||||
e := httptest.New(t, app)
|
||||
for _, method := range router.AllMethods {
|
||||
|
||||
e.Request(method, "/").Expect().Status(httptest.StatusOK).
|
||||
e.Request(method, "/").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(method)
|
||||
|
||||
e.Request(method, "/all").Expect().Status(httptest.StatusOK).
|
||||
e.Request(method, "/all").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(method)
|
||||
|
||||
e.Request(method, "/any").Expect().Status(httptest.StatusOK).
|
||||
e.Request(method, "/any").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(method)
|
||||
}
|
||||
}
|
||||
@@ -89,13 +89,13 @@ func TestControllerMethodAndPathHandleMany(t *testing.T) {
|
||||
e := httptest.New(t, app)
|
||||
for _, method := range router.AllMethods {
|
||||
|
||||
e.Request(method, "/").Expect().Status(httptest.StatusOK).
|
||||
e.Request(method, "/").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(method)
|
||||
|
||||
e.Request(method, "/path1").Expect().Status(httptest.StatusOK).
|
||||
e.Request(method, "/path1").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(method)
|
||||
|
||||
e.Request(method, "/path2").Expect().Status(httptest.StatusOK).
|
||||
e.Request(method, "/path2").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(method)
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func TestControllerPersistenceFields(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.Controller("/", &testControllerPersistence{Data: data})
|
||||
e := httptest.New(t, app)
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(data)
|
||||
}
|
||||
|
||||
@@ -165,9 +165,9 @@ func TestControllerBeginAndEndRequestFunc(t *testing.T) {
|
||||
doneResponse := "done"
|
||||
|
||||
for _, username := range usernames {
|
||||
e.GET("/profile/" + username).Expect().Status(httptest.StatusOK).
|
||||
e.GET("/profile/" + username).Expect().Status(iris.StatusOK).
|
||||
Body().Equal(username + doneResponse)
|
||||
e.POST("/profile/" + username).Expect().Status(httptest.StatusOK).
|
||||
e.POST("/profile/" + username).Expect().Status(iris.StatusOK).
|
||||
Body().Equal(username + doneResponse)
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@ func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.StatusCode(httptest.StatusForbidden)
|
||||
ctx.StatusCode(iris.StatusForbidden)
|
||||
ctx.Writef("forbidden")
|
||||
}
|
||||
|
||||
@@ -203,18 +203,18 @@ func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) {
|
||||
for username, allow := range usernames {
|
||||
getEx := e.GET("/profile/" + username).Expect()
|
||||
if allow {
|
||||
getEx.Status(httptest.StatusOK).
|
||||
getEx.Status(iris.StatusOK).
|
||||
Body().Equal(username + doneResponse)
|
||||
} else {
|
||||
getEx.Status(httptest.StatusForbidden).Body().Equal("forbidden")
|
||||
getEx.Status(iris.StatusForbidden).Body().Equal("forbidden")
|
||||
}
|
||||
|
||||
postEx := e.POST("/profile/" + username).Expect()
|
||||
if allow {
|
||||
postEx.Status(httptest.StatusOK).
|
||||
postEx.Status(iris.StatusOK).
|
||||
Body().Equal(username + doneResponse)
|
||||
} else {
|
||||
postEx.Status(httptest.StatusForbidden).Body().Equal("forbidden")
|
||||
postEx.Status(iris.StatusForbidden).Body().Equal("forbidden")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,7 +275,7 @@ func TestControllerModel(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, username := range usernames {
|
||||
e.GET("/model/" + username).Expect().Status(httptest.StatusOK).
|
||||
e.GET("/model/" + username).Expect().Status(iris.StatusOK).
|
||||
Body().Equal(username + username + "2")
|
||||
}
|
||||
}
|
||||
@@ -318,9 +318,9 @@ func TestControllerBind(t *testing.T) {
|
||||
|
||||
e := httptest.New(t, app)
|
||||
expected := t1 + t2
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(expected)
|
||||
e.GET("/deep").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/deep").Expect().Status(iris.StatusOK).
|
||||
Body().Equal(expected)
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ func TestControllerRelPathAndRelTmpl(t *testing.T) {
|
||||
|
||||
e := httptest.New(t, app)
|
||||
for path, tt := range tests {
|
||||
e.GET(path).Expect().Status(httptest.StatusOK).JSON().Equal(tt)
|
||||
e.GET(path).Expect().Status(iris.StatusOK).JSON().Equal(tt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ func TestControllerInsideControllerRecursively(t *testing.T) {
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.GET("/user/" + username).Expect().
|
||||
Status(httptest.StatusOK).Body().Equal(expected)
|
||||
Status(iris.StatusOK).Body().Equal(expected)
|
||||
}
|
||||
|
||||
type testControllerRelPathFromFunc struct{ mvc.Controller }
|
||||
@@ -467,35 +467,35 @@ func TestControllerRelPathFromFunc(t *testing.T) {
|
||||
app.Controller("/", new(testControllerRelPathFromFunc))
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/")
|
||||
|
||||
e.GET("/42").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/42").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/42")
|
||||
e.GET("/something/true").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/something/true").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/something/true")
|
||||
e.GET("/something/false").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/something/false").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/something/false")
|
||||
e.GET("/something/truee").Expect().Status(httptest.StatusNotFound)
|
||||
e.GET("/something/falsee").Expect().Status(httptest.StatusNotFound)
|
||||
e.GET("/something/kataras/42").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/something/truee").Expect().Status(iris.StatusNotFound)
|
||||
e.GET("/something/falsee").Expect().Status(iris.StatusNotFound)
|
||||
e.GET("/something/kataras/42").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/something/kataras/42")
|
||||
e.GET("/something/new/kataras/42").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/something/new/kataras/42").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/something/new/kataras/42")
|
||||
e.GET("/something/true/else/this/42").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/something/true/else/this/42").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/something/true/else/this/42")
|
||||
|
||||
e.GET("/login").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/login").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/login")
|
||||
e.POST("/login").Expect().Status(httptest.StatusOK).
|
||||
e.POST("/login").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("POST:/login")
|
||||
e.GET("/admin/login").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/admin/login").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/admin/login")
|
||||
e.PUT("/something/into/this").Expect().Status(httptest.StatusOK).
|
||||
e.PUT("/something/into/this").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("PUT:/something/into/this")
|
||||
e.GET("/42").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/42").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/42")
|
||||
e.GET("/anything/here").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/anything/here").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("GET:/anything/here")
|
||||
}
|
||||
|
||||
@@ -523,8 +523,8 @@ func TestControllerActivateListener(t *testing.T) {
|
||||
})
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("default title")
|
||||
e.GET("/manual").Expect().Status(httptest.StatusOK).
|
||||
e.GET("/manual").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("my title")
|
||||
}
|
||||
|
||||
30
mvc/go19.go
30
mvc/go19.go
@@ -3,17 +3,25 @@
|
||||
package mvc
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"github.com/kataras/iris/mvc/activator"
|
||||
)
|
||||
|
||||
// ActivatePayload contains the necessary information and the ability
|
||||
// to alt a controller's registration options, i.e the binder.
|
||||
//
|
||||
// With `ActivatePayload` the `Controller` can register custom routes
|
||||
// or modify the provided values that will be binded to the
|
||||
// controller later on.
|
||||
//
|
||||
// Look the `mvc/activator#ActivatePayload` for its implementation.
|
||||
//
|
||||
// A shortcut for the `mvc/activator#ActivatePayload`, useful when `OnActivate` is being used.
|
||||
type ActivatePayload = activator.ActivatePayload
|
||||
type (
|
||||
// HTML wraps the "s" with the template.HTML
|
||||
// in order to be marked as safe content, to be rendered as html and not escaped.
|
||||
HTML = template.HTML
|
||||
|
||||
// ActivatePayload contains the necessary information and the ability
|
||||
// to alt a controller's registration options, i.e the binder.
|
||||
//
|
||||
// With `ActivatePayload` the `Controller` can register custom routes
|
||||
// or modify the provided values that will be binded to the
|
||||
// controller later on.
|
||||
//
|
||||
// Look the `mvc/activator#ActivatePayload` for its implementation.
|
||||
//
|
||||
// A shortcut for the `mvc/activator#ActivatePayload`, useful when `OnActivate` is being used.
|
||||
ActivatePayload = activator.ActivatePayload
|
||||
)
|
||||
|
||||
58
mvc/method_result.go
Normal file
58
mvc/method_result.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package mvc
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/mvc/activator/methodfunc"
|
||||
)
|
||||
|
||||
// build go1.9 only(go19.go)-->
|
||||
// // Result is a response dispatcher.
|
||||
// // All types that complete this interface
|
||||
// // can be returned as values from the method functions.
|
||||
// Result = methodfunc.Result
|
||||
// <--
|
||||
// No, let's just copy-paste in order to go 1.8 users have this type
|
||||
// easy to be used from the root mvc package,
|
||||
// sometimes duplication doesn't hurt.
|
||||
|
||||
// Result is a response dispatcher.
|
||||
// All types that complete this interface
|
||||
// can be returned as values from the method functions.
|
||||
//
|
||||
// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/using-method-result.
|
||||
type Result interface { // NOTE: Should be always compatible with the methodfunc.Result.
|
||||
// Dispatch should sends the response to the context's response writer.
|
||||
Dispatch(ctx context.Context)
|
||||
}
|
||||
|
||||
var defaultFailureResponse = Response{Code: methodfunc.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/mvc/method_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
|
||||
}
|
||||
43
mvc/method_result_response.go
Normal file
43
mvc/method_result_response.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package mvc
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/mvc/activator/methodfunc"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// if not empty then content type is the text/plain
|
||||
// and content is the text as []byte.
|
||||
Text string
|
||||
// If not nil then it will fire that as "application/json" or the
|
||||
// "ContentType" if not empty.
|
||||
Object interface{}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var _ methodfunc.Result = Response{}
|
||||
|
||||
// Dispatch writes the response result to the context's response writer.
|
||||
func (r Response) Dispatch(ctx context.Context) {
|
||||
if s := r.Text; s != "" {
|
||||
r.Content = []byte(s)
|
||||
}
|
||||
|
||||
methodfunc.DispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, r.Err)
|
||||
}
|
||||
224
mvc/method_result_test.go
Normal file
224
mvc/method_result_test.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package mvc_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/httptest"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// activator/methodfunc/func_caller.go.
|
||||
// and activator/methodfunc/func_result_dispatcher.go
|
||||
|
||||
type testControllerMethodResult struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResult) Get() mvc.Result {
|
||||
return mvc.Response{
|
||||
Text: "Hello World!",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResult) GetWithStatus() mvc.Response { // or mvc.Result again, no problem.
|
||||
return mvc.Response{
|
||||
Text: "This page doesn't exist",
|
||||
Code: iris.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
type testCustomStruct struct {
|
||||
Name string `json:"name" xml:"name"`
|
||||
Age int `json:"age" xml:"age"`
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResult) GetJson() mvc.Result {
|
||||
var err error
|
||||
if c.Ctx.URLParamExists("err") {
|
||||
err = errors.New("error here")
|
||||
}
|
||||
return mvc.Response{
|
||||
Err: err, // if err != nil then it will fire the error's text with a BadRequest.
|
||||
Object: testCustomStruct{Name: "Iris", Age: 2},
|
||||
}
|
||||
}
|
||||
|
||||
var things = []string{"thing 0", "thing 1", "thing 2"}
|
||||
|
||||
func (c *testControllerMethodResult) GetThingWithTryBy(index int) mvc.Result {
|
||||
failure := mvc.Response{
|
||||
Text: "thing does not exist",
|
||||
Code: iris.StatusNotFound,
|
||||
}
|
||||
|
||||
return mvc.Try(func() mvc.Result {
|
||||
// if panic because of index exceed the slice
|
||||
// then the "failure" response will be returned instead.
|
||||
return mvc.Response{Text: things[index]}
|
||||
}, failure)
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) mvc.Result {
|
||||
return mvc.Try(func() mvc.Result {
|
||||
// if panic because of index exceed the slice
|
||||
// then the default failure response will be returned instead (400 bad request).
|
||||
return mvc.Response{Text: things[index]}
|
||||
})
|
||||
}
|
||||
|
||||
func TestControllerMethodResult(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.Controller("/", new(testControllerMethodResult))
|
||||
|
||||
e := httptest.New(t, app)
|
||||
|
||||
e.GET("/").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("Hello World!")
|
||||
|
||||
e.GET("/with/status").Expect().Status(iris.StatusNotFound).
|
||||
Body().Equal("This page doesn't exist")
|
||||
|
||||
e.GET("/json").Expect().Status(iris.StatusOK).
|
||||
JSON().Equal(iris.Map{
|
||||
"name": "Iris",
|
||||
"age": 2,
|
||||
})
|
||||
|
||||
e.GET("/json").WithQuery("err", true).Expect().
|
||||
Status(iris.StatusBadRequest).
|
||||
Body().Equal("error here")
|
||||
|
||||
e.GET("/thing/with/try/1").Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().Equal("thing 1")
|
||||
// failure because of index exceed the slice
|
||||
e.GET("/thing/with/try/3").Expect().
|
||||
Status(iris.StatusNotFound).
|
||||
Body().Equal("thing does not exist")
|
||||
|
||||
e.GET("/thing/with/try/default/3").Expect().
|
||||
Status(iris.StatusBadRequest).
|
||||
Body().Equal("Bad Request")
|
||||
}
|
||||
|
||||
type testControllerMethodResultTypes struct {
|
||||
mvc.Controller
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetText() string {
|
||||
return "text"
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetStatus() int {
|
||||
return iris.StatusBadGateway
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetTextWithStatusOk() (string, int) {
|
||||
return "OK", iris.StatusOK
|
||||
}
|
||||
|
||||
// tests should have output arguments mixed
|
||||
func (c *testControllerMethodResultTypes) GetStatusWithTextNotOkBy(first string, second string) (int, string) {
|
||||
return iris.StatusForbidden, "NOT_OK_" + first + second
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetTextAndContentType() (string, string) {
|
||||
return "<b>text</b>", "text/html"
|
||||
}
|
||||
|
||||
type testControllerMethodCustomResult struct {
|
||||
HTML string
|
||||
}
|
||||
|
||||
// The only one required function to make that a custom Response dispatcher.
|
||||
func (r testControllerMethodCustomResult) Dispatch(ctx context.Context) {
|
||||
ctx.HTML(r.HTML)
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetCustomResponse() testControllerMethodCustomResult {
|
||||
return testControllerMethodCustomResult{"<b>text</b>"}
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusOk() (testControllerMethodCustomResult, int) {
|
||||
return testControllerMethodCustomResult{"<b>OK</b>"}, iris.StatusOK
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusNotOk() (testControllerMethodCustomResult, int) {
|
||||
return testControllerMethodCustomResult{"<b>internal server error</b>"}, iris.StatusInternalServerError
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetCustomStruct() testCustomStruct {
|
||||
return testCustomStruct{"Iris", 2}
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetCustomStructWithStatusNotOk() (testCustomStruct, int) {
|
||||
return testCustomStruct{"Iris", 2}, iris.StatusInternalServerError
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetCustomStructWithContentType() (testCustomStruct, string) {
|
||||
return testCustomStruct{"Iris", 2}, "text/xml"
|
||||
}
|
||||
|
||||
func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCustomStruct, err error) {
|
||||
s = testCustomStruct{"Iris", 2}
|
||||
if c.Ctx.URLParamExists("err") {
|
||||
err = errors.New("omit return of testCustomStruct and fire error")
|
||||
}
|
||||
|
||||
// it should send the testCustomStruct as JSON if error is nil
|
||||
// otherwise it should fire the default error(BadRequest) with the error's text.
|
||||
return
|
||||
}
|
||||
|
||||
func TestControllerMethodResultTypes(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.Controller("/", new(testControllerMethodResultTypes))
|
||||
|
||||
e := httptest.New(t, app)
|
||||
|
||||
e.GET("/text").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("text")
|
||||
|
||||
e.GET("/status").Expect().Status(iris.StatusBadGateway)
|
||||
|
||||
e.GET("/text/with/status/ok").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("OK")
|
||||
|
||||
e.GET("/status/with/text/not/ok/first/second").Expect().Status(iris.StatusForbidden).
|
||||
Body().Equal("NOT_OK_firstsecond")
|
||||
|
||||
e.GET("/text/and/content/type").Expect().Status(iris.StatusOK).
|
||||
ContentType("text/html", "utf-8").
|
||||
Body().Equal("<b>text</b>")
|
||||
|
||||
e.GET("/custom/response").Expect().Status(iris.StatusOK).
|
||||
ContentType("text/html", "utf-8").
|
||||
Body().Equal("<b>text</b>")
|
||||
e.GET("/custom/response/with/status/ok").Expect().Status(iris.StatusOK).
|
||||
ContentType("text/html", "utf-8").
|
||||
Body().Equal("<b>OK</b>")
|
||||
e.GET("/custom/response/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
|
||||
ContentType("text/html", "utf-8").
|
||||
Body().Equal("<b>internal server error</b>")
|
||||
|
||||
expectedResultFromCustomStruct := map[string]interface{}{
|
||||
"name": "Iris",
|
||||
"age": 2,
|
||||
}
|
||||
e.GET("/custom/struct").Expect().Status(iris.StatusOK).
|
||||
JSON().Equal(expectedResultFromCustomStruct)
|
||||
e.GET("/custom/struct/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
|
||||
JSON().Equal(expectedResultFromCustomStruct)
|
||||
e.GET("/custom/struct/with/content/type").Expect().Status(iris.StatusOK).
|
||||
ContentType("text/xml", "utf-8")
|
||||
e.GET("/custom/struct/with/error").Expect().Status(iris.StatusOK).
|
||||
JSON().Equal(expectedResultFromCustomStruct)
|
||||
e.GET("/custom/struct/with/error").WithQuery("err", true).Expect().
|
||||
Status(iris.StatusBadRequest). // the default status code if error is not nil
|
||||
// the content should be not JSON it should be the status code's text
|
||||
// it will fire the error's text
|
||||
Body().Equal("omit return of testCustomStruct and fire error")
|
||||
}
|
||||
77
mvc/method_result_view.go
Normal file
77
mvc/method_result_view.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package mvc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/mvc/activator/methodfunc"
|
||||
)
|
||||
|
||||
// View completes the `methodfunc.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.
|
||||
//
|
||||
// Example at: https://github.com/kataras/iris/blob/master/_examples/mvc/using-method-result/controllers/hello_controller.go.
|
||||
type View struct {
|
||||
Name string
|
||||
Layout string
|
||||
Data interface{} // map or a custom struct.
|
||||
Code int
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ methodfunc.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 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.
|
||||
if r.Err != nil {
|
||||
if r.Code < 400 {
|
||||
r.Code = methodfunc.DefaultErrStatusCode
|
||||
}
|
||||
ctx.StatusCode(r.Code)
|
||||
ctx.WriteString(r.Err.Error())
|
||||
ctx.StopExecution()
|
||||
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 {
|
||||
ctx.Values().Set(
|
||||
ctx.Application().ConfigurationReadOnly().GetViewDataContextKey(),
|
||||
r.Data,
|
||||
)
|
||||
}
|
||||
|
||||
ctx.View(r.Name)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user