mirror of
https://github.com/kataras/iris.git
synced 2026-01-06 19:47:05 +00:00
Update to 8.3.3 | Even better MVC. Read HISTORY.md
Former-commit-id: 88998c317117abff1a214cf396b205d8d8ea888c
This commit is contained in:
@@ -151,8 +151,8 @@ func ActivateController(base BaseController, bindValues []interface{},
|
||||
binder := newBinder(typ.Elem(), bindValues)
|
||||
if binder != nil {
|
||||
for _, bf := range binder.fields {
|
||||
golog.Debugf("MVC %s: binder loaded for '%s' with field index of: %d",
|
||||
ctrlName, bf.Name, bf.Index)
|
||||
golog.Debugf("MVC %s: binder loaded for '%s' with value:\n%#v",
|
||||
ctrlName, bf.getFullName(), bf.getValue())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,79 +66,19 @@ func (b *binder) lookup(elem reflect.Type) (fields []field) {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, n := 0, elem.NumField(); i < n; i++ {
|
||||
elemField := elem.Field(i)
|
||||
if elemField.Type == value.Type() {
|
||||
// we area inside the correct type
|
||||
// println("[0] prepare bind filed for " + elemField.Name)
|
||||
fields = append(fields, field{
|
||||
Index: i,
|
||||
Name: elemField.Name,
|
||||
Type: elemField.Type,
|
||||
Value: value,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
f := lookupStruct(elemField.Type, value)
|
||||
if f != nil {
|
||||
fields = append(fields, field{
|
||||
Index: i,
|
||||
Name: elemField.Name,
|
||||
Type: elemField.Type,
|
||||
embedded: f,
|
||||
})
|
||||
}
|
||||
|
||||
matcher := func(elemField reflect.StructField) bool {
|
||||
return elemField.Type == value.Type()
|
||||
}
|
||||
|
||||
handler := func(f *field) {
|
||||
f.Value = value
|
||||
}
|
||||
|
||||
fields = append(fields, lookupFields(elem, matcher, handler)...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lookupStruct(elem reflect.Type, value reflect.Value) *field {
|
||||
// ignore if that field is not a struct
|
||||
if elem.Kind() != reflect.Struct {
|
||||
// and it's not a controller because we don't want to accidentally
|
||||
// set fields to other user fields. Or no?
|
||||
// ||
|
||||
// (elem.Name() != "" && !strings.HasSuffix(elem.Name(), "Controller")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// search by fields.
|
||||
for i, n := 0, elem.NumField(); i < n; i++ {
|
||||
elemField := elem.Field(i)
|
||||
if elemField.Type == value.Type() {
|
||||
// println("Types are equal of: " + elemField.Type.Name() + " " + elemField.Name + " and " + value.Type().Name())
|
||||
// we area inside the correct type.
|
||||
return &field{
|
||||
Index: i,
|
||||
Name: elemField.Name,
|
||||
Type: elemField.Type,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// if field is struct and the value is struct
|
||||
// then try inside its fields for a compatible
|
||||
// field type.
|
||||
if elemField.Type.Kind() == reflect.Struct && value.Type().Kind() == reflect.Struct {
|
||||
elemFieldEmb := elem.Field(i)
|
||||
f := lookupStruct(elemFieldEmb.Type, value)
|
||||
if f != nil {
|
||||
fp := &field{
|
||||
Index: i,
|
||||
Name: elemFieldEmb.Name,
|
||||
Type: elemFieldEmb.Type,
|
||||
embedded: f,
|
||||
}
|
||||
return fp
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *binder) handle(c reflect.Value) {
|
||||
// we could make check for middlewares here but
|
||||
// these could easly be used outside of the controller
|
||||
|
||||
217
mvc/activator/field.go
Normal file
217
mvc/activator/field.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package activator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type field struct {
|
||||
Name string // the field's original name
|
||||
// but if a tag with `name: "other"`
|
||||
// exist then this fill is filled, otherwise it's the same as the Name.
|
||||
TagName string
|
||||
Index int
|
||||
Type reflect.Type
|
||||
Value reflect.Value
|
||||
|
||||
embedded *field
|
||||
}
|
||||
|
||||
// getIndex returns all the "dimensions"
|
||||
// of the controller struct field's position that this field is referring to,
|
||||
// recursively.
|
||||
// Usage: elem.FieldByIndex(field.getIndex())
|
||||
// for example the {0,1} means that the field is on the second field of the first's
|
||||
// field of this struct.
|
||||
func (ff field) getIndex() []int {
|
||||
deepIndex := []int{ff.Index}
|
||||
|
||||
if emb := ff.embedded; emb != nil {
|
||||
deepIndex = append(deepIndex, emb.getIndex()...)
|
||||
}
|
||||
|
||||
return deepIndex
|
||||
}
|
||||
|
||||
// getType returns the type of the referring field, recursively.
|
||||
func (ff field) getType() reflect.Type {
|
||||
typ := ff.Type
|
||||
if emb := ff.embedded; emb != nil {
|
||||
return emb.getType()
|
||||
}
|
||||
|
||||
return typ
|
||||
}
|
||||
|
||||
// getFullName returns the full name of that field
|
||||
// i.e: UserController.SessionController.Manager,
|
||||
// it's useful for debugging only.
|
||||
func (ff field) getFullName() string {
|
||||
name := ff.Name
|
||||
|
||||
if emb := ff.embedded; emb != nil {
|
||||
return name + "." + emb.getFullName()
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// getTagName returns the tag name of the referring field
|
||||
// recursively.
|
||||
func (ff field) getTagName() string {
|
||||
name := ff.TagName
|
||||
|
||||
if emb := ff.embedded; emb != nil {
|
||||
return emb.getTagName()
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// checkVal checks if that value
|
||||
// is valid to be set-ed to the new controller's instance.
|
||||
// Used on binder.
|
||||
func checkVal(val reflect.Value) bool {
|
||||
return val.IsValid() && (val.Kind() == reflect.Ptr && !val.IsNil()) && val.CanInterface()
|
||||
}
|
||||
|
||||
// getValue returns a valid value of the referring field, recursively.
|
||||
func (ff field) getValue() interface{} {
|
||||
if ff.embedded != nil {
|
||||
return ff.embedded.getValue()
|
||||
}
|
||||
|
||||
if checkVal(ff.Value) {
|
||||
return ff.Value.Interface()
|
||||
}
|
||||
|
||||
return "undefinied value"
|
||||
}
|
||||
|
||||
// sendTo should be used when this field or its embedded
|
||||
// has a Value on it.
|
||||
// It sets the field's value to the "elem" instance, it's the new controller.
|
||||
func (ff field) sendTo(elem reflect.Value) {
|
||||
// note:
|
||||
// we don't use the getters here
|
||||
// because we do recursively search by our own here
|
||||
// to be easier to debug if ever needed.
|
||||
if embedded := ff.embedded; embedded != nil {
|
||||
if ff.Index >= 0 {
|
||||
embedded.sendTo(elem.Field(ff.Index))
|
||||
}
|
||||
return
|
||||
}
|
||||
elemField := elem.Field(ff.Index)
|
||||
if elemField.Kind() == reflect.Ptr && !elemField.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
elemField.Set(ff.Value)
|
||||
}
|
||||
|
||||
// lookupTagName checks if the "elemField" struct's field
|
||||
// contains a tag `name`, if it contains then it returns its value
|
||||
// otherwise returns the field's original Name.
|
||||
func lookupTagName(elemField reflect.StructField) string {
|
||||
vname := elemField.Name
|
||||
|
||||
if taggedName, ok := elemField.Tag.Lookup("name"); ok {
|
||||
vname = taggedName
|
||||
}
|
||||
return vname
|
||||
}
|
||||
|
||||
// lookupFields iterates all "elem"'s fields and its fields
|
||||
// if structs, recursively.
|
||||
// Compares them to the "matcher", if they passed
|
||||
// then it executes the "handler" if any,
|
||||
// the handler can change the field as it wants to.
|
||||
//
|
||||
// It finally returns that collection of the valid fields, can be empty.
|
||||
func lookupFields(elem reflect.Type, matcher func(reflect.StructField) bool, handler func(*field)) (fields []field) {
|
||||
for i, n := 0, elem.NumField(); i < n; i++ {
|
||||
elemField := elem.Field(i)
|
||||
if matcher(elemField) {
|
||||
field := field{
|
||||
Index: i,
|
||||
Name: elemField.Name,
|
||||
TagName: lookupTagName(elemField),
|
||||
Type: elemField.Type,
|
||||
}
|
||||
|
||||
if handler != nil {
|
||||
handler(&field)
|
||||
}
|
||||
|
||||
// we area inside the correct type
|
||||
fields = append(fields, field)
|
||||
continue
|
||||
}
|
||||
|
||||
f := lookupStructField(elemField.Type, matcher, handler)
|
||||
if f != nil {
|
||||
fields = append(fields, field{
|
||||
Index: i,
|
||||
Name: elemField.Name,
|
||||
Type: elemField.Type,
|
||||
embedded: f,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// lookupStructField is here to search for embedded field only,
|
||||
// is working with the "lookupFields".
|
||||
// We could just one one function
|
||||
// for both structured (embedded) fields and normal fields
|
||||
// but we keep that as it's, a new function like this
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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{
|
||||
Index: i,
|
||||
Name: elemField.Name,
|
||||
TagName: lookupTagName(elemField),
|
||||
Type: elemField.Type,
|
||||
}
|
||||
|
||||
if handler != nil {
|
||||
handler(f)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// if field is struct and the value is struct
|
||||
// then try inside its fields for a compatible
|
||||
// field type.
|
||||
if elemField.Type.Kind() == reflect.Struct { // 3-level
|
||||
elemFieldEmb := elem.Field(i)
|
||||
f := lookupStructField(elemFieldEmb.Type, matcher, handler)
|
||||
if f != nil {
|
||||
fp := &field{
|
||||
Index: i,
|
||||
Name: elemFieldEmb.Name,
|
||||
TagName: lookupTagName(elemFieldEmb),
|
||||
Type: elemFieldEmb.Type,
|
||||
embedded: f,
|
||||
}
|
||||
return fp
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -11,14 +11,16 @@ type modelControl struct {
|
||||
}
|
||||
|
||||
func (mc *modelControl) Load(t *TController) error {
|
||||
fields := lookupFields(t, func(f reflect.StructField) bool {
|
||||
matcher := func(f reflect.StructField) bool {
|
||||
if tag, ok := f.Tag.Lookup("iris"); ok {
|
||||
if tag == "model" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
fields := lookupFields(t.Type.Elem(), matcher, nil)
|
||||
|
||||
if len(fields) == 0 {
|
||||
// first is the `Controller` so we need to
|
||||
@@ -34,15 +36,22 @@ func (mc *modelControl) Handle(ctx context.Context, c reflect.Value, methodFunc
|
||||
elem := c.Elem() // controller should always be a pointer at this state
|
||||
|
||||
for _, f := range mc.fields {
|
||||
elemField := elem.Field(f.Index)
|
||||
|
||||
index := f.getIndex()
|
||||
typ := f.getType()
|
||||
name := f.getTagName()
|
||||
|
||||
elemField := elem.FieldByIndex(index)
|
||||
// check if current controller's element field
|
||||
// is valid, is not nil and it's type is the same (should be but make that check to be sure).
|
||||
if !elemField.IsValid() || (elemField.Kind() == reflect.Ptr && elemField.IsNil()) || elemField.Type() != f.Type {
|
||||
if !elemField.IsValid() ||
|
||||
(elemField.Kind() == reflect.Ptr && elemField.IsNil()) ||
|
||||
elemField.Type() != typ {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := elemField.Interface()
|
||||
// fmt.Printf("setting %s to %#v", f.Name, fieldValue)
|
||||
ctx.ViewData(f.Name, fieldValue)
|
||||
ctx.ViewData(name, fieldValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,77 +6,31 @@ import (
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
type field struct {
|
||||
Name string // by-defaultis the field's name but if `name: "other"` then it's overridden.
|
||||
Index int
|
||||
Type reflect.Type
|
||||
Value reflect.Value
|
||||
|
||||
embedded *field
|
||||
}
|
||||
|
||||
func (ff field) sendTo(elem reflect.Value) {
|
||||
if embedded := ff.embedded; embedded != nil {
|
||||
if ff.Index >= 0 {
|
||||
embedded.sendTo(elem.Field(ff.Index))
|
||||
}
|
||||
return
|
||||
}
|
||||
elemField := elem.Field(ff.Index)
|
||||
if elemField.Kind() == reflect.Ptr && !elemField.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
elemField.Set(ff.Value)
|
||||
}
|
||||
|
||||
func lookupFields(t *TController, validator func(reflect.StructField) bool) (fields []field) {
|
||||
elem := t.Type.Elem()
|
||||
|
||||
for i, n := 0, elem.NumField(); i < n; i++ {
|
||||
elemField := elem.Field(i)
|
||||
valF := t.Value.Field(i)
|
||||
|
||||
// catch persistence data by tags, i.e:
|
||||
// MyData string `iris:"persistence"`
|
||||
if validator(elemField) {
|
||||
name := elemField.Name
|
||||
if nameTag, ok := elemField.Tag.Lookup("name"); ok {
|
||||
name = nameTag
|
||||
}
|
||||
|
||||
f := field{
|
||||
Name: name,
|
||||
Index: i,
|
||||
Type: elemField.Type,
|
||||
}
|
||||
|
||||
if valF.IsValid() || (valF.Kind() == reflect.Ptr && !valF.IsNil()) {
|
||||
val := reflect.ValueOf(valF.Interface())
|
||||
if val.IsValid() || (val.Kind() == reflect.Ptr && !val.IsNil()) {
|
||||
f.Value = val
|
||||
}
|
||||
}
|
||||
|
||||
fields = append(fields, f)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type persistenceDataControl struct {
|
||||
fields []field
|
||||
}
|
||||
|
||||
func (d *persistenceDataControl) Load(t *TController) error {
|
||||
fields := lookupFields(t, func(f reflect.StructField) bool {
|
||||
if tag, ok := f.Tag.Lookup("iris"); ok {
|
||||
matcher := func(elemField reflect.StructField) bool {
|
||||
if tag, ok := elemField.Tag.Lookup("iris"); ok {
|
||||
if tag == "persistence" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
handler := func(f *field) {
|
||||
valF := t.Value.Field(f.Index)
|
||||
if valF.IsValid() || (valF.Kind() == reflect.Ptr && !valF.IsNil()) {
|
||||
val := reflect.ValueOf(valF.Interface())
|
||||
if val.IsValid() || (val.Kind() == reflect.Ptr && !val.IsNil()) {
|
||||
f.Value = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fields := lookupFields(t.Type.Elem(), matcher, handler)
|
||||
|
||||
if len(fields) == 0 {
|
||||
// first is the `Controller` so we need to
|
||||
|
||||
@@ -104,8 +104,8 @@ type testControllerPersistence struct {
|
||||
Data string `iris:"persistence"`
|
||||
}
|
||||
|
||||
func (t *testControllerPersistence) Get() {
|
||||
t.Ctx.WriteString(t.Data)
|
||||
func (c *testControllerPersistence) Get() {
|
||||
c.Ctx.WriteString(c.Data)
|
||||
}
|
||||
|
||||
func TestControllerPersistenceFields(t *testing.T) {
|
||||
@@ -127,25 +127,25 @@ type testControllerBeginAndEndRequestFunc struct {
|
||||
//
|
||||
// useful when more than one methods using the
|
||||
// same request values or context's function calls.
|
||||
func (t *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) {
|
||||
t.Controller.BeginRequest(ctx)
|
||||
t.Username = ctx.Params().Get("username")
|
||||
func (c *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) {
|
||||
c.Controller.BeginRequest(ctx)
|
||||
c.Username = ctx.Params().Get("username")
|
||||
// or t.Params.Get("username") because the
|
||||
// t.Ctx == ctx and is being initialized at the t.Controller.BeginRequest.
|
||||
}
|
||||
|
||||
// called after every method (Get() or Post()).
|
||||
func (t *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
|
||||
func (c *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
|
||||
ctx.Writef("done") // append "done" to the response
|
||||
t.Controller.EndRequest(ctx)
|
||||
c.Controller.EndRequest(ctx)
|
||||
}
|
||||
|
||||
func (t *testControllerBeginAndEndRequestFunc) Get() {
|
||||
t.Ctx.Writef(t.Username)
|
||||
func (c *testControllerBeginAndEndRequestFunc) Get() {
|
||||
c.Ctx.Writef(c.Username)
|
||||
}
|
||||
|
||||
func (t *testControllerBeginAndEndRequestFunc) Post() {
|
||||
t.Ctx.Writef(t.Username)
|
||||
func (c *testControllerBeginAndEndRequestFunc) Post() {
|
||||
c.Ctx.Writef(c.Username)
|
||||
}
|
||||
|
||||
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
|
||||
@@ -229,47 +229,39 @@ type testControllerModel struct {
|
||||
TestModel2 Model `iris:"model"`
|
||||
}
|
||||
|
||||
func (t *testControllerModel) Get() {
|
||||
username := t.Ctx.Params().Get("username")
|
||||
t.TestModel = Model{Username: username}
|
||||
t.TestModel2 = Model{Username: username + "2"}
|
||||
func (c *testControllerModel) Get() {
|
||||
username := c.Ctx.Params().Get("username")
|
||||
c.TestModel = Model{Username: username}
|
||||
c.TestModel2 = Model{Username: username + "2"}
|
||||
}
|
||||
|
||||
func (t *testControllerModel) EndRequest(ctx context.Context) {
|
||||
// t.Ctx == ctx
|
||||
|
||||
m, ok := t.Ctx.GetViewData()["myModel"]
|
||||
if !ok {
|
||||
t.Ctx.Writef("fail TestModel load and set")
|
||||
func writeModels(ctx context.Context, names ...string) {
|
||||
if expected, got := len(names), len(ctx.GetViewData()); expected != got {
|
||||
ctx.Writef("expected view data length: %d but got: %d for names: %s", expected, got, names)
|
||||
return
|
||||
}
|
||||
|
||||
model, ok := m.(Model)
|
||||
for _, name := range names {
|
||||
|
||||
if !ok {
|
||||
t.Ctx.Writef("fail to override the TestModel name by the tag")
|
||||
return
|
||||
m, ok := ctx.GetViewData()[name]
|
||||
if !ok {
|
||||
ctx.Writef("fail load and set the %s", name)
|
||||
return
|
||||
}
|
||||
|
||||
model, ok := m.(Model)
|
||||
if !ok {
|
||||
ctx.Writef("fail to override the %s' name by the tag", name)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Writef(model.Username)
|
||||
}
|
||||
}
|
||||
|
||||
// test without custom name tag, should have the field's nae.
|
||||
m, ok = t.Ctx.GetViewData()["TestModel2"]
|
||||
if !ok {
|
||||
t.Ctx.Writef("fail TestModel2 load and set")
|
||||
return
|
||||
}
|
||||
|
||||
model2, ok := m.(Model)
|
||||
|
||||
if !ok {
|
||||
t.Ctx.Writef("fail to override the TestModel2 name by the tag")
|
||||
return
|
||||
}
|
||||
|
||||
// models are being rendered via the View at ViewData but
|
||||
// we just test it here, so print it back.
|
||||
t.Ctx.Writef(model.Username + model2.Username)
|
||||
|
||||
t.Controller.EndRequest(ctx)
|
||||
func (c *testControllerModel) EndRequest(ctx context.Context) {
|
||||
writeModels(ctx, "myModel", "TestModel2")
|
||||
c.Controller.EndRequest(ctx)
|
||||
}
|
||||
func TestControllerModel(t *testing.T) {
|
||||
app := iris.New()
|
||||
@@ -313,6 +305,7 @@ func (t *testControllerBindDeep) Get() {
|
||||
}
|
||||
func TestControllerBind(t *testing.T) {
|
||||
app := iris.New()
|
||||
|
||||
t1, t2 := "my pointer title", "val title"
|
||||
// test bind pointer to pointer of the correct type
|
||||
myTitlePtr := &testBindType{title: t1}
|
||||
@@ -384,5 +377,62 @@ func TestControllerRelPathAndRelTmpl(t *testing.T) {
|
||||
for path, tt := range tests {
|
||||
e.GET(path).Expect().Status(httptest.StatusOK).JSON().Equal(tt)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type testCtrl0 struct {
|
||||
testCtrl00
|
||||
}
|
||||
|
||||
func (c *testCtrl0) Get() {
|
||||
username := c.Params.Get("username")
|
||||
c.Model = Model{Username: username}
|
||||
}
|
||||
|
||||
func (c *testCtrl0) EndRequest(ctx context.Context) {
|
||||
writeModels(ctx, "myModel")
|
||||
|
||||
if c.TitlePointer == nil {
|
||||
ctx.Writef("\nTitlePointer is nil!\n")
|
||||
} else {
|
||||
ctx.Writef(c.TitlePointer.title)
|
||||
}
|
||||
|
||||
//should be the same as `.testCtrl000.testCtrl0000.EndRequest(ctx)`
|
||||
c.testCtrl00.EndRequest(ctx)
|
||||
}
|
||||
|
||||
type testCtrl00 struct {
|
||||
testCtrl000
|
||||
|
||||
Model Model `iris:"model" name:"myModel"`
|
||||
}
|
||||
|
||||
type testCtrl000 struct {
|
||||
testCtrl0000
|
||||
|
||||
TitlePointer *testBindType
|
||||
}
|
||||
|
||||
type testCtrl0000 struct {
|
||||
mvc.Controller
|
||||
}
|
||||
|
||||
func (c *testCtrl0000) EndRequest(ctx context.Context) {
|
||||
ctx.Writef("finish")
|
||||
}
|
||||
|
||||
func TestControllerInsideControllerRecursively(t *testing.T) {
|
||||
var (
|
||||
username = "gerasimos"
|
||||
title = "mytitle"
|
||||
expected = username + title + "finish"
|
||||
)
|
||||
|
||||
app := iris.New()
|
||||
app.Controller("/user/{username}", new(testCtrl0),
|
||||
&testBindType{title: title})
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.GET("/user/" + username).Expect().
|
||||
Status(httptest.StatusOK).Body().Equal(expected)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user