OK, my dream-idea is implemented. TODO: Some examples and doc.go is not updated yet, comments on the mvc/di subpackage, the tutorial/vuejs-todo-mvc is running but not finished yet (it's using browser's localstorage and it should be replaced by the http requests that are registered via iris mvc

Former-commit-id: 0ea7e01ce1d78bcb78b40f3b0f5c03ad7c9abaea
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-12-16 06:38:28 +02:00
parent 55dfd195e0
commit 34664aa311
41 changed files with 437 additions and 408 deletions

View File

@ -1,19 +1,8 @@
package controllers
import "github.com/kataras/iris/mvc"
// ValuesController is the equivalent
// `ValuesController` of the .net core 2.0 mvc application.
type ValuesController struct {
mvc.C
}
/* on windows tests(older) the Get was:
func (vc *ValuesController) Get() {
// id,_ := vc.Params.GetInt("id")
// vc.Ctx.WriteString("value")
}
but as Iris is always going better, now supports return values as well*/
type ValuesController struct{}
// Get handles "GET" requests to "api/values/{id}".
func (vc *ValuesController) Get() string {

View File

@ -1,15 +1,18 @@
package main
/// TODO: remove this on the "master" branch, or even replace it
// with the "iris-mvc" (the new implementatioin is even faster, close to handlers version,
// with bindings or without).
import (
"github.com/kataras/iris/_benchmarks/iris-mvc2/controllers"
"github.com/kataras/iris"
"github.com/kataras/iris/_benchmarks/iris-mvc/controllers"
"github.com/kataras/iris/mvc"
)
func main() {
app := iris.New()
app.Controller("/api/values/{id}", new(controllers.ValuesController))
// 24 August 2017: Iris has a built'n version updater but we don't need it
// when benchmarking...
mvc.New(app.Party("/api/values/{id}")).Register(new(controllers.ValuesController))
app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
}

View File

@ -1,25 +0,0 @@
package controllers
// import "github.com/kataras/iris/mvc2"
// ValuesController is the equivalent
// `ValuesController` of the .net core 2.0 mvc application.
type ValuesController struct{} //{ mvc2.C }
/* on windows tests(older) the Get was:
func (vc *ValuesController) Get() {
// id,_ := vc.Params.GetInt("id")
// vc.Ctx.WriteString("value")
}
but as Iris is always going better, now supports return values as well*/
// Get handles "GET" requests to "api/values/{id}".
func (vc *ValuesController) Get() string {
return "value"
}
// Put handles "PUT" requests to "api/values/{id}".
func (vc *ValuesController) Put() {}
// Delete handles "DELETE" requests to "api/values/{id}".
func (vc *ValuesController) Delete() {}

View File

@ -1,17 +0,0 @@
package main
/// TODO: remove this on the "master" branch, or even replace it
// with the "iris-mvc" (the new implementatioin is even faster, close to handlers version,
// with bindings or without).
import (
"github.com/kataras/iris"
"github.com/kataras/iris/_benchmarks/iris-mvc2/controllers"
"github.com/kataras/iris/mvc2"
)
func main() {
app := iris.New()
mvc2.New().Controller(app.Party("/api/values/{id}"), new(controllers.ValuesController))
app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
}

View File

@ -1,13 +1,10 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
import "github.com/kataras/iris"
func main() {
app := iris.New()
app.Get("/api/values/{id}", func(ctx context.Context) {
app.Get("/api/values/{id}", func(ctx iris.Context) {
ctx.WriteString("value")
})

View File

@ -3,13 +3,6 @@ package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
// auto-completion does not working well with type aliases
// when embedded fields.
// We should complete a report on golang repo for that at some point.
//
// Therefore import the "mvc" package manually
// here at "hello-world" so users can see that
// import path somewhere else than the "FAQ" section.
"github.com/kataras/iris/middleware/logger"
"github.com/kataras/iris/middleware/recover"
@ -43,27 +36,18 @@ func main() {
app.Use(recover.New())
app.Use(logger.New())
app.Controller("/", new(ExampleController))
// Register a controller based on the root Router, "/".
mvc.New(app).Register(new(ExampleController))
// http://localhost:8080
// http://localhost:8080/ping
// http://localhost:8080/hello
// http://localhost:8080/custom_path
app.Run(iris.Addr(":8080"))
}
// ExampleController serves the "/", "/ping" and "/hello".
type ExampleController struct {
// if you build with go1.8 you have to use the mvc package always,
// otherwise
// you can, optionally
// use the type alias `iris.C`,
// same for
// context.Context -> iris.Context,
// mvc.Result -> iris.Result,
// mvc.Response -> iris.Response,
// mvc.View -> iris.View
mvc.C
}
type ExampleController struct{}
// Get serves
// Method: GET
@ -89,6 +73,31 @@ func (c *ExampleController) GetHello() interface{} {
return map[string]string{"message": "Hello Iris!"}
}
// BeforeActivate called once, before the controller adapted to the main application
// and of course before the server ran.
// After version 9 you can also add custom routes for a specific controller's methods.
// Here you can register custom method's handlers
// use the standard router with `ca.Router` to do something that you can do without mvc as well,
// and add dependencies that will be binded to a controller's fields or method function's input arguments.
func (c *ExampleController) BeforeActivate(ca *mvc.ControllerActivator) {
anyMiddlewareHere := func(ctx iris.Context) {
ctx.Application().Logger().Warnf("Inside /custom_path")
ctx.Next()
}
ca.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere)
// or even add a global middleware based on this controller's router,
// which in this example is the root "/":
// ca.Router.Use(myMiddleware)
}
// CustomHandlerWithoutFollowingTheNamingGuide serves
// Method: GET
// Resource: http://localhost:8080/custom_path
func (c *ExampleController) CustomHandlerWithoutFollowingTheNamingGuide() string {
return "hello from the custom handler without following the naming guide"
}
// GetUserBy serves
// Method: GET
// Resource: http://localhost:8080/user/{username:string}
@ -121,4 +130,14 @@ func (c *ExampleController) Trace() {}
func (c *ExampleController) All() {}
// OR
func (c *ExampleController) Any() {}
func (c *ExampleController) BeforeActivate(ca *mvc.ControllerActivator) {
// 1 -> the HTTP Method
// 2 -> the route's path
// 3 -> this controller's method name that should be handler for that route.
ca.Handle("GET", "/mypath/{param}", "DoIt", optionalMiddlewareHere...)
}
*/

View File

@ -10,38 +10,52 @@ import (
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
)
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
// Load the template files.
app.RegisterView(iris.HTML("./web/views", ".html"))
// Register our controllers.
app.Controller("/hello", new(controllers.HelloController))
mvc.New(app.Party("/hello")).Register(new(controllers.HelloController))
// You can also split the code you write to configure an mvc.Application
// using the `Configure` method, as shown below.
mvc.New(app.Party("/movies")).Configure(movies)
// Create our movie repository with some (memory) data from the datasource.
repo := repositories.NewMovieRepository(datasource.Movies)
// Create our movie service, we will bind it to the movie controller.
movieService := services.NewMovieService(repo)
app.Controller("/movies", new(controllers.MovieController),
// Bind the "movieService" to the MovieController's Service (interface) field.
movieService,
// Add the basic authentication(admin:password) middleware
// for the /movies based requests.
middleware.BasicAuth)
// Start the web server at localhost:8080
// http://localhost:8080/hello
// http://localhost:8080/hello/iris
// http://localhost:8080/movies
// http://localhost:8080/movies/1
app.Run(
// Start the web server at localhost:8080
iris.Addr("localhost:8080"),
// disables updates:
iris.WithoutVersionChecker,
// skip err server closed when CTRL/CMD+C pressed:
iris.WithoutServerError(iris.ErrServerClosed),
iris.WithOptimizations, // enables faster json serialization and more
// enables faster json serialization and more:
iris.WithOptimizations,
)
}
// note the mvc.Application, it's not iris.Application.
func movies(app *mvc.Application) {
// Add the basic authentication(admin:password) middleware
// for the /movies based requests.
app.Router.Use(middleware.BasicAuth)
// Create our movie repository with some (memory) data from the datasource.
repo := repositories.NewMovieRepository(datasource.Movies)
// Create our movie service, we will bind it to the movie app's dependencies.
movieService := services.NewMovieService(repo)
app.AddDependencies(movieService)
// Register our movies controller.
// Note that you can register more than one controller
// you can alos create child mvc apps using the `movies.NewChild()` if you want.
app.Register(new(controllers.MovieController))
}

View File

@ -10,9 +10,7 @@ import (
// HelloController is our sample controller
// it handles GET: /hello and GET: /hello/{name}
type HelloController struct {
mvc.C
}
type HelloController struct{}
var helloView = mvc.View{
Name: "hello/index.html",
@ -32,7 +30,7 @@ func (c *HelloController) Get() mvc.Result {
return helloView
}
// you can define a standard error in order to be re-usable anywhere in your app.
// you can define a standard error in order to re-use anywhere in your app.
var errBadName = errors.New("bad name")
// you can just return it as error or even better

View File

@ -9,17 +9,10 @@ import (
"github.com/kataras/iris/_examples/mvc/overview/services"
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
)
// MovieController is our /movies controller.
type MovieController struct {
// mvc.C is just a lightweight lightweight alternative
// to the "mvc.Controller" controller type,
// use it when you don't need mvc.Controller's fields
// (you don't need those fields when you return values from the method functions).
mvc.C
// Our MovieService, it's an interface which
// is binded from the main application.
Service services.MovieService
@ -53,9 +46,9 @@ func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
// PutBy updates a movie.
// Demo:
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
func (c *MovieController) PutBy(ctx iris.Context, id int64) (datamodels.Movie, error) {
// get the request data for poster and genre
file, info, err := c.Ctx.FormFile("poster")
file, info, err := ctx.FormFile("poster")
if err != nil {
return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
}
@ -64,7 +57,7 @@ func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
// imagine that is the url of the uploaded file...
poster := info.Filename
genre := c.Ctx.FormValue("genre")
genre := ctx.FormValue("genre")
return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
}

View File

@ -0,0 +1 @@
# Unfinished - wait until today :)

View File

@ -4,8 +4,9 @@ import (
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
"github.com/kataras/iris"
mvc "github.com/kataras/iris/mvc2"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/mvc"
)
// TodoController is our TODO app's web controller.
@ -38,10 +39,6 @@ func (c *TodoController) BeforeActivate(ca *mvc.ControllerActivator) {
})
// ca.Router.Use(...).Done(...).Layout(...)
// TODO:(?)
// m := ca.Method("PutCompleteBy")
// m.Route.Use(...).Done(...) <- we don't have the route here but I can find something to solve this.
// m.Dependencies.Add(...)
}
// Get handles the GET: /todo route.

View File

@ -5,8 +5,9 @@ import (
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/web/controllers"
"github.com/kataras/iris"
mvc "github.com/kataras/iris/mvc2"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/mvc"
)
func main() {
@ -22,14 +23,16 @@ func main() {
Cookie: "_iris_session",
})
m := mvc.New()
m := mvc.New(app.Party("/todo"))
// any bindings here...
m.Bind(mvc.Session(sess))
// any dependencies bindings here...
m.AddDependencies(
mvc.Session(sess),
new(todo.MemoryService),
)
m.Bind(new(todo.MemoryService))
// controllers registration here...
m.Controller(app.Party("/todo"), new(controllers.TodoController))
m.Register(new(controllers.TodoController))
// start the web server at http://localhost:8080
app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker, iris.WithOptimizations)

4
mvc/AUTHORS Normal file
View File

@ -0,0 +1,4 @@
# This is the official list of Iris MVC authors for copyright
# purposes.
Gerasimos Maropoulos <kataras2006@hotmail.com>

27
mvc/LICENSE Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2018 Gerasimos Maropoulos. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Iris nor the names of its
contributor, Gerasimos Maropoulos, may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,78 +1,25 @@
package mvc2
package mvc
import (
"fmt"
"reflect"
"strings"
"github.com/kataras/iris/mvc2/di"
"github.com/kataras/iris/mvc/di"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
"github.com/kataras/iris/core/router/macro"
)
// BaseController is the controller interface,
// which the main request `C` will implement automatically.
// End-dev doesn't need to have any knowledge of this if she/he doesn't want to implement
// a new Controller type.
// Controller looks the whole flow as one handler, so `ctx.Next`
// inside `BeginRequest` is not be respected.
// Alternative way to check if a middleware was procceed successfully
// and called its `ctx.Next` is the `ctx.Proceed(handler) bool`.
// You have to navigate to the `context/context#Proceed` function's documentation.
// BaseController is the optional controller interface, if it's
// completed by the end controller then the BeginRequest and EndRequest
// are called between the controller's method responsible for the incoming request.
type BaseController interface {
BeginRequest(context.Context)
EndRequest(context.Context)
}
// C is the basic BaseController type that can be used as an embedded anonymous field
// to custom end-dev controllers.
//
// func(c *ExampleController) Get() string |
// (string, string) |
// (string, int) |
// int |
// (int, string |
// (string, error) |
// bool |
// (any, bool) |
// 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/overview/web/controllers.
// Example usage at: https://github.com/kataras/iris/blob/master/mvc/method_result_test.go#L17.
type C struct {
// 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 _ BaseController = &C{}
// BeginRequest does nothing anymore, is here to complet ethe `BaseController` interface.
// BaseController is not required anymore, `Ctx` is binded automatically by the engine's
// wrapped Handler.
func (c *C) BeginRequest(ctx context.Context) {}
// EndRequest does nothing, is here to complete the `BaseController` interface.
func (c *C) EndRequest(ctx context.Context) {}
// ControllerActivator returns a new controller type info description.
// Its functionality can be overriden by the end-dev.
type ControllerActivator struct {
@ -244,7 +191,10 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
// get the function's input arguments' bindings.
funcDependencies := c.Dependencies.Clone()
funcDependencies.AddValue(pathParams...)
// fmt.Printf("for %s | values: %s\n", funcName, funcDependencies.Values)
funcInjector := funcDependencies.Func(m.Func)
// fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length)
// the element value, not the pointer, wil lbe used to create a
// new controller on each incoming request.

View File

@ -1,29 +1,22 @@
package mvc2_test
package mvc_test
import (
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/httptest"
. "github.com/kataras/iris/mvc2"
. "github.com/kataras/iris/mvc"
)
type testControllerHandle struct {
C
Ctx context.Context
Service TestService
reqField string
}
func (c *testControllerHandle) Get() string {
return "index"
}
func (c *testControllerHandle) BeginRequest(ctx iris.Context) {
c.C.BeginRequest(ctx)
c.reqField = ctx.URLParam("reqfield")
}
func (c *testControllerHandle) BeforeActivate(ca *ControllerActivator) { // BeforeActivate(t *mvc.TController) {
ca.Handle("GET", "/histatic", "HiStatic")
ca.Handle("GET", "/hiservice", "HiService")
@ -31,6 +24,16 @@ func (c *testControllerHandle) BeforeActivate(ca *ControllerActivator) { // Befo
ca.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
}
func (c *testControllerHandle) BeginRequest(ctx iris.Context) {
c.reqField = ctx.URLParam("reqfield")
}
func (c *testControllerHandle) EndRequest(ctx iris.Context) {}
func (c *testControllerHandle) Get() string {
return "index"
}
func (c *testControllerHandle) HiStatic() string {
return c.reqField
}

View File

@ -1,4 +1,4 @@
package mvc2
package mvc
import (
"errors"
@ -106,10 +106,21 @@ var posWords = map[int]string{
7: "seventh",
8: "eighth",
9: "ninth",
10: "tenth",
11: "eleventh",
12: "twelfth",
13: "thirteenth",
14: "fourteenth",
15: "fifteenth",
16: "sixteenth",
17: "seventeenth",
18: "eighteenth",
19: "nineteenth",
20: "twentieth",
}
func genParamKey(argIdx int) string {
return "param" + posWords[argIdx] // paramfirst, paramsecond...
return "arg" + posWords[argIdx] // argfirst, argsecond...
}
type methodParser struct {
@ -176,7 +187,7 @@ func (p *methodParser) parse() (method, path string, err error) {
// continue
// }
if path, err = p.parsePathParam(path, w, funcArgPos); err != nil {
if path, funcArgPos, err = p.parsePathParam(path, w, funcArgPos); err != nil {
return "", "", err
}
@ -184,24 +195,22 @@ func (p *methodParser) parse() (method, path string, err error) {
}
// static path.
path += "/" + strings.ToLower(w)
}
return
}
func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (string, error) {
func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (string, int, error) {
typ := p.fn.Type
if typ.NumIn() <= funcArgPos {
// By found but input arguments are not there, so act like /by path without restricts.
path += "/" + strings.ToLower(w)
return path, nil
return path, funcArgPos, nil
}
var (
paramKey = genParamKey(funcArgPos) // paramfirst, paramsecond...
paramKey = genParamKey(funcArgPos) // argfirst, argsecond...
paramType = ast.ParamTypeString // default string
)
@ -216,10 +225,19 @@ func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (st
// it's not wildcard, so check base on our available macro types.
paramType = pType
} else {
return "", errors.New("invalid syntax for " + p.fn.Name)
if typ.NumIn() > funcArgPos {
// has more input arguments but we are not in the correct
// index now, maybe the first argument was an `iris/context.Context`
// so retry with the "funcArgPos" incremented.
//
// the "funcArgPos" will be updated to the caller as well
// because we return it as well.
return p.parsePathParam(path, w, funcArgPos+1)
}
return "", 0, errors.New("invalid syntax for " + p.fn.Name)
}
// /{paramfirst:path}, /{paramfirst:long}...
// /{argfirst:path}, /{argfirst:long}...
path += fmt.Sprintf("/{%s:%s}", paramKey, paramType.String())
if nextWord == "" && typ.NumIn() > funcArgPos+1 {
@ -232,5 +250,5 @@ func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (st
return p.parsePathParam(path, nextWord, funcArgPos+1)
}
return path, nil
return path, funcArgPos, nil
}

View File

@ -1,5 +1,5 @@
// black-box testing
package mvc2_test
package mvc_test
import (
"testing"
@ -8,56 +8,57 @@ import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
"github.com/kataras/iris/httptest"
. "github.com/kataras/iris/mvc2"
. "github.com/kataras/iris/mvc"
)
type testController struct {
C
Ctx context.Context
}
var writeMethod = func(c C) {
c.Ctx.Writef(c.Ctx.Method())
var writeMethod = func(ctx context.Context) {
ctx.Writef(ctx.Method())
}
func (c *testController) Get() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testController) Post() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testController) Put() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testController) Delete() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testController) Connect() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testController) Head() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testController) Patch() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testController) Options() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testController) Trace() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
type (
testControllerAll struct{ C }
testControllerAny struct{ C } // exactly the same as All.
testControllerAll struct{ Ctx context.Context }
testControllerAny struct{ Ctx context.Context } // exactly the same as All.
)
func (c *testControllerAll) All() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func (c *testControllerAny) Any() {
writeMethod(c.C)
writeMethod(c.Ctx)
}
func TestControllerMethodFuncs(t *testing.T) {
@ -83,7 +84,7 @@ func TestControllerMethodFuncs(t *testing.T) {
}
type testControllerBeginAndEndRequestFunc struct {
C
Ctx context.Context
Username string
}
@ -93,14 +94,12 @@ type testControllerBeginAndEndRequestFunc struct {
// useful when more than one methods using the
// same request values or context's function calls.
func (c *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) {
c.C.BeginRequest(ctx)
c.Username = ctx.Params().Get("username")
}
// called after every method (Get() or Post()).
func (c *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
ctx.Writef("done") // append "done" to the response
c.C.EndRequest(ctx)
}
func (c *testControllerBeginAndEndRequestFunc) Get() {
@ -187,7 +186,7 @@ type Model struct {
}
type testControllerEndRequestAwareness struct {
C
Ctx context.Context
}
func (c *testControllerEndRequestAwareness) Get() {
@ -223,9 +222,9 @@ func writeModels(ctx context.Context, names ...string) {
}
}
func (c *testControllerEndRequestAwareness) BeginRequest(ctx context.Context) {}
func (c *testControllerEndRequestAwareness) EndRequest(ctx context.Context) {
writeModels(ctx, "TestModel", "myModel")
c.C.EndRequest(ctx)
}
func TestControllerEndRequestAwareness(t *testing.T) {
@ -249,7 +248,8 @@ type testBindType struct {
}
type testControllerBindStruct struct {
C
Ctx context.Context
// should start with upper letter of course
TitlePointer *testBindType // should have the value of the "myTitlePtr" on test
TitleValue testBindType // should have the value of the "myTitleV" on test
@ -320,6 +320,8 @@ func (c *testCtrl0) EndRequest(ctx context.Context) {
}
type testCtrl00 struct {
Ctx context.Context
testCtrl000
}
@ -330,9 +332,9 @@ type testCtrl000 struct {
}
type testCtrl0000 struct {
C
}
func (c *testCtrl0000) BeginRequest(ctx context.Context) {}
func (c *testCtrl0000) EndRequest(ctx context.Context) {
ctx.Writef("finish")
}
@ -354,11 +356,11 @@ func TestControllerInsideControllerRecursively(t *testing.T) {
Status(iris.StatusOK).Body().Equal(expected)
}
type testControllerRelPathFromFunc struct{ C }
type testControllerRelPathFromFunc struct{}
func (c *testControllerRelPathFromFunc) BeginRequest(ctx context.Context) {}
func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) {
ctx.Writef("%s:%s", ctx.Method(), ctx.Path())
c.C.EndRequest(ctx)
}
func (c *testControllerRelPathFromFunc) Get() {}
@ -416,8 +418,6 @@ func TestControllerRelPathFromFunc(t *testing.T) {
}
type testControllerActivateListener struct {
C
TitlePointer *testBindType
}

View File

@ -1,8 +1,6 @@
package di
import (
"reflect"
)
import "reflect"
type (
targetFuncInput struct {
@ -50,7 +48,6 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, v
InputIndex: i,
Object: b,
})
continue
}
}
@ -69,6 +66,7 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, v
}
if b.IsAssignable(inTyp) {
// println(inTyp.String() + " is assignable to " + val.Type().String())
// fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
// i, b.Type.String(), val.String(), val.Pointer())
s.inputs = append(s.inputs, &targetFuncInput{
@ -82,8 +80,9 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, v
}
}
s.Length = n
s.Valid = len(s.inputs) > 0
// s.Length = n
s.Length = len(s.inputs)
s.Valid = s.Length > 0
return s
}

View File

@ -1,8 +1,6 @@
package di
import (
"reflect"
)
import "reflect"
type Values []reflect.Value

105
mvc/engine.go Normal file
View File

@ -0,0 +1,105 @@
package mvc
import (
"github.com/kataras/golog"
"github.com/kataras/iris/mvc/di"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
)
// Engine contains the Dependencies which will be binded
// to the controller(s) or handler(s) that can be created
// using the Engine's `Handler` and `Controller` methods.
//
// This is not exported for being used by everyone, use it only when you want
// to share engines between multi mvc.go#Application
// or make custom mvc handlers that can be used on the standard
// iris' APIBuilder. The last one reason is the most useful here,
// although end-devs can use the `MakeHandler` as well.
//
// For a more high-level structure please take a look at the "mvc.go#Application".
type Engine struct {
Dependencies *di.D
}
// NewEngine returns a new engine, a container for dependencies and a factory
// for handlers and controllers, this is used internally by the `mvc#Application` structure.
// Please take a look at the structure's documentation for more information.
func NewEngine() *Engine {
return &Engine{
Dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
}
}
// Clone creates and returns a new engine with the parent's(current) Dependencies.
// It copies the current "e" dependencies and returns a new engine.
func (e *Engine) Clone() *Engine {
child := NewEngine()
child.Dependencies = e.Dependencies.Clone()
return child
}
// Handler accepts a "handler" function which can accept any input arguments that match
// with the Engine's `Dependencies` and any output result; like string, int (string,int),
// custom structs, Result(View | Response) and anything you already know that mvc implementation supports.
// It returns a standard `iris/context.Handler` which can be used anywhere in an Iris Application,
// as middleware or as simple route handler or subdomain's handler.
func (e *Engine) Handler(handler interface{}) context.Handler {
h, err := MakeHandler(handler, e.Dependencies.Values...)
if err != nil {
golog.Errorf("mvc handler: %v", err)
}
return h
}
// Controller accepts a sub router and registers any custom struct
// as controller, if struct doesn't have any compatible methods
// neither are registered via `ControllerActivator`'s `Handle` method
// then the controller is not registered at all.
//
// A Controller may have one or more methods
// that are wrapped to a handler and registered as routes before the server ran.
// The controller's method can accept any input argument that are previously binded
// via the dependencies or route's path accepts dynamic path parameters.
// The controller's fields are also bindable via the dependencies, either a
// static value (service) or a function (dynamically) which accepts a context
// and returns a single value (this type is being used to find the relative field or method's input argument).
//
// func(c *ExampleController) Get() string |
// (string, string) |
// (string, int) |
// int |
// (int, string |
// (string, error) |
// bool |
// (any, bool) |
// error |
// (int, error) |
// (customStruct, error) |
// customStruct |
// (customStruct, int) |
// (customStruct, string) |
// Result or (Result, error)
// where Get is an HTTP Method func.
//
// Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc.
func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(*ControllerActivator)) {
ca := newControllerActivator(router, controller, e.Dependencies)
// give a priority to the "beforeActivate"
// callbacks, if any.
for _, cb := range beforeActivate {
cb(ca)
}
// check if controller has an "BeforeActivate" function
// which accepts the controller activator and call it.
if activateListener, ok := controller.(interface {
BeforeActivate(*ControllerActivator)
}); ok {
activateListener.BeforeActivate(ca)
}
ca.activate()
}

View File

@ -1,11 +1,11 @@
package mvc2_test
package mvc_test
// black-box in combination with the handler_test
import (
"testing"
. "github.com/kataras/iris/mvc2"
. "github.com/kataras/iris/mvc"
)
func TestMvcEngineInAndHandler(t *testing.T) {

View File

@ -1,11 +1,11 @@
package mvc2
package mvc
import (
"reflect"
"strings"
"github.com/fatih/structs"
"github.com/kataras/iris/mvc2/di"
"github.com/kataras/iris/mvc/di"
"github.com/kataras/iris/context"
)

View File

@ -1,4 +1,4 @@
package mvc2_test
package mvc_test
import (
"errors"
@ -7,14 +7,15 @@ import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/httptest"
. "github.com/kataras/iris/mvc2"
. "github.com/kataras/iris/mvc"
)
// activator/methodfunc/func_caller.go.
// and activator/methodfunc/func_result_dispatcher.go
type testControllerMethodResult struct {
C
Ctx context.Context
}
func (c *testControllerMethodResult) Get() Result {
@ -105,7 +106,7 @@ func TestControllerMethodResult(t *testing.T) {
}
type testControllerMethodResultTypes struct {
C
Ctx context.Context
}
func (c *testControllerMethodResultTypes) GetText() string {
@ -227,16 +228,13 @@ func TestControllerMethodResultTypes(t *testing.T) {
type testControllerViewResultRespectCtxViewData struct {
T *testing.T
C
}
func (t *testControllerViewResultRespectCtxViewData) BeginRequest(ctx context.Context) {
t.C.BeginRequest(ctx)
ctx.ViewData("name_begin", "iris_begin")
}
func (t *testControllerViewResultRespectCtxViewData) EndRequest(ctx context.Context) {
t.C.EndRequest(ctx)
// check if data is not overridden by return View {Data: context.Map...}
dataWritten := ctx.GetViewData()

View File

@ -1,8 +1,8 @@
package mvc2
package mvc
import (
"fmt"
"github.com/kataras/iris/mvc2/di"
"github.com/kataras/iris/mvc/di"
"reflect"
"runtime"
@ -39,8 +39,8 @@ func MustMakeHandler(handler interface{}, bindValues ...reflect.Value) context.H
return h
}
// MakeHandler accepts a "handler" function which can accept any input that matches
// with the "binders" and any output, that matches the mvc types, like string, int (string,int),
// MakeHandler accepts a "handler" function which can accept any input arguments that match
// with the "bindValues" types and any output result, that matches the mvc types, like string, int (string,int),
// custom structs, Result(View | Response) and anything that you already know that mvc implementation supports,
// and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application,
// as middleware or as simple route handler or party handler or subdomain handler-router.

View File

@ -1,4 +1,4 @@
package mvc2_test
package mvc_test
// black-box
@ -9,7 +9,8 @@ import (
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
. "github.com/kataras/iris/mvc2"
. "github.com/kataras/iris/mvc"
)
// dynamic func

View File

@ -6,7 +6,7 @@ import (
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
mvc "github.com/kataras/iris/mvc2"
"github.com/kataras/iris/mvc"
)
func main() {

View File

@ -1,4 +1,4 @@
package mvc2
package mvc
import "github.com/kataras/iris/core/router"

View File

@ -1,4 +1,4 @@
package mvc2
package mvc
import (
"reflect"
@ -16,20 +16,36 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type)
return
}
funcInIdx := 0
// it's a valid param type.
for _, p := range params {
in := funcIn[funcInIdx]
consumedParams := make(map[int]bool, 0)
for _, in := range funcIn {
for j, p := range params {
if _, consumed := consumedParams[j]; consumed {
continue
}
paramType := p.Type
paramName := p.Name
// fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String())
if paramType.Assignable(in.Kind()) {
consumedParams[j] = true
// fmt.Printf("path_param_binder.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String())
values = append(values, makeFuncParamGetter(paramType, paramName))
}
funcInIdx++
}
}
// funcInIdx := 0
// // it's a valid param type.
// for _, p := range params {
// in := funcIn[funcInIdx]
// paramType := p.Type
// paramName := p.Name
// // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String())
// if paramType.Assignable(in.Kind()) {
// // fmt.Printf("path_param_binder.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String())
// values = append(values, makeFuncParamGetter(paramType, paramName))
// }
// funcInIdx++
// }
return
}

View File

@ -1,4 +1,4 @@
package mvc2
package mvc
import (
"testing"

54
mvc/reflect.go Normal file
View File

@ -0,0 +1,54 @@
package mvc
import (
"reflect"
"github.com/kataras/iris/context"
"github.com/kataras/iris/mvc/di"
)
var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
func isBaseController(ctrlTyp reflect.Type) bool {
return ctrlTyp.Implements(baseControllerTyp)
}
var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem()
func isContext(inTyp reflect.Type) bool {
return inTyp.Implements(contextTyp)
}
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
n := funcTyp.NumIn()
funcIn := make([]reflect.Type, n, n)
for i := 0; i < n; i++ {
funcIn[i] = funcTyp.In(i)
}
return funcIn
}
var (
typeChecker = func(fn reflect.Type) bool {
// valid if that single input arg is a typeof context.Context.
return fn.NumIn() == 1 && isContext(fn.In(0))
}
hijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) {
if !isContext(fieldOrFuncInput) {
return nil, false
}
// this is being used on both func injector and struct injector.
// if the func's input argument or the struct's field is a type of Context
// then we can do a fast binding using the ctxValue
// which is used as slice of reflect.Value, because of the final method's `Call`.
return &di.BindObject{
Type: contextTyp,
BindType: di.Dynamic,
ReturnValue: func(ctxValue []reflect.Value) reflect.Value {
return ctxValue[0]
},
}, true
}
)

View File

@ -1,4 +1,4 @@
package mvc2
package mvc
import (
"github.com/kataras/iris/context"

View File

@ -1,4 +1,4 @@
package mvc2
package mvc
import (
"github.com/kataras/iris/context"
@ -11,8 +11,6 @@ var defaultSessionManager = sessions.New(sessions.Config{})
// which requires a binded session manager in order to give
// direct access to the current client's session via its `Session` field.
type SessionController struct {
C
Manager *sessions.Sessions
Session *sessions.Session
}
@ -30,10 +28,8 @@ func (s *SessionController) BeforeActivate(ca *ControllerActivator) {
}
}
// BeginRequest calls the Controller's BeginRequest
// and tries to initialize the current user's Session.
// BeginRequest initializes the current user's Session.
func (s *SessionController) BeginRequest(ctx context.Context) {
s.C.BeginRequest(ctx)
if s.Manager == nil {
ctx.Application().Logger().Errorf(`MVC SessionController: sessions manager is nil, report this as a bug
because the SessionController should predict this on its activation state and use a default one automatically`)
@ -42,3 +38,6 @@ because the SessionController should predict this on its activation state and us
s.Session = s.Manager.Start(ctx)
}
// EndRequest is here to complete the `BaseController`.
func (s *SessionController) EndRequest(ctx context.Context) {}

View File

@ -1,34 +0,0 @@
package mvc2
import (
"github.com/kataras/iris/mvc2/di"
"reflect"
)
var (
typeChecker = func(fn reflect.Type) bool {
// invalid if that single input arg is not a typeof context.Context.
return isContext(fn.In(0))
}
hijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) {
if isContext(fieldOrFuncInput) {
return newContextBindObject(), true
}
return nil, false
}
)
// newContextBindObject is being used on both targetFunc and targetStruct.
// if the func's input argument or the struct's field is a type of Context
// then we can do a fast binding using the ctxValue
// which is used as slice of reflect.Value, because of the final method's `Call`.
func newContextBindObject() *di.BindObject {
return &di.BindObject{
Type: contextTyp,
BindType: di.Dynamic,
ReturnValue: func(ctxValue []reflect.Value) reflect.Value {
return ctxValue[0]
},
}
}

View File

@ -1,53 +0,0 @@
package mvc2
import (
"github.com/kataras/iris/mvc2/di"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
)
type Engine struct {
Dependencies *di.D
}
func NewEngine() *Engine {
return &Engine{
Dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
}
}
func (e *Engine) Clone() *Engine {
child := NewEngine()
child.Dependencies = e.Dependencies.Clone()
return child
}
func (e *Engine) Handler(handler interface{}) context.Handler {
h, err := MakeHandler(handler, e.Dependencies.Values...)
if err != nil {
golog.Errorf("mvc handler: %v", err)
}
return h
}
func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(*ControllerActivator)) {
ca := newControllerActivator(router, controller, e.Dependencies)
// give a priority to the "beforeActivate"
// callbacks, if any.
for _, cb := range beforeActivate {
cb(ca)
}
// check if controller has an "BeforeActivate" function
// which accepts the controller activator and call it.
if activateListener, ok := controller.(interface {
BeforeActivate(*ControllerActivator)
}); ok {
activateListener.BeforeActivate(ca)
}
ca.activate()
}

View File

@ -1,28 +0,0 @@
package mvc2
import (
"reflect"
"github.com/kataras/iris/context"
)
var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
func isBaseController(ctrlTyp reflect.Type) bool {
return ctrlTyp.Implements(baseControllerTyp)
}
var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem()
func isContext(inTyp reflect.Type) bool {
return inTyp.Implements(contextTyp)
}
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
n := funcTyp.NumIn()
funcIn := make([]reflect.Type, n, n)
for i := 0; i < n; i++ {
funcIn[i] = funcTyp.In(i)
}
return funcIn
}