mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:16:28 +01:00
finish the first state of the vuejs todo mvc example, a simple rest api - todo: websocket and live updates between browser tabs with the same session id
Former-commit-id: 0bd859420cff87014479c44a471ec273c621c1a2
This commit is contained in:
parent
a2f217be17
commit
e1c65d23fb
|
@ -14,7 +14,7 @@ import (
|
||||||
// DELETE /users/{id:long} | delete by id
|
// DELETE /users/{id:long} | delete by id
|
||||||
// Requires basic authentication.
|
// Requires basic authentication.
|
||||||
type UsersController struct {
|
type UsersController struct {
|
||||||
// context is auto-binded by Iris on each request,
|
// Optionally: context is auto-binded by Iris on each request,
|
||||||
// remember that on each incoming request iris creates a new UserController each time,
|
// remember that on each incoming request iris creates a new UserController each time,
|
||||||
// so all fields are request-scoped by-default, only dependency injection is able to set
|
// so all fields are request-scoped by-default, only dependency injection is able to set
|
||||||
// custom fields like the Service which is the same for all requests (static binding).
|
// custom fields like the Service which is the same for all requests (static binding).
|
||||||
|
@ -84,5 +84,5 @@ func (c *UsersController) DeleteBy(id int64) interface{} {
|
||||||
// right here we can see that a method function
|
// right here we can see that a method function
|
||||||
// can return any of those two types(map or int),
|
// can return any of those two types(map or int),
|
||||||
// we don't have to specify the return type to a specific type.
|
// we don't have to specify the return type to a specific type.
|
||||||
return 400 // same as `iris.StatusBadRequest`.
|
return iris.StatusBadRequest // same as 400.
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,8 @@
|
||||||
package todo
|
package todo
|
||||||
|
|
||||||
type State uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
StateActive State = iota
|
|
||||||
StateCompleted
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseState(s string) State {
|
|
||||||
switch s {
|
|
||||||
case "completed":
|
|
||||||
return StateCompleted
|
|
||||||
default:
|
|
||||||
return StateActive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item struct {
|
type Item struct {
|
||||||
OwnerID string
|
SessionID string `json:"-"`
|
||||||
ID int64
|
ID int64 `json:"id,omitempty"`
|
||||||
Body string
|
Title string `json:"title"`
|
||||||
CurrentState State
|
Completed bool `json:"completed"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,31 @@
|
||||||
package todo
|
package todo
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
GetByID(id int64) (Item, bool)
|
Get(owner string) []Item
|
||||||
GetByOwner(owner string) []Item
|
Save(owner string, newItems []Item) error
|
||||||
Complete(item Item) bool
|
|
||||||
Save(newItem Item) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryService struct {
|
type MemoryService struct {
|
||||||
items map[int64]Item
|
items map[string][]Item
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryService) getLatestID() (id int64) {
|
func NewMemoryService() *MemoryService {
|
||||||
for k := range s.items {
|
return &MemoryService{make(map[string][]Item, 0)}
|
||||||
if k > id {
|
}
|
||||||
id = k
|
|
||||||
|
func (s *MemoryService) Get(sessionOwner string) (items []Item) {
|
||||||
|
return s.items[sessionOwner]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryService) Save(sessionOwner string, newItems []Item) error {
|
||||||
|
var prevID int64
|
||||||
|
for i := range newItems {
|
||||||
|
if newItems[i].ID == 0 {
|
||||||
|
newItems[i].ID = prevID
|
||||||
|
prevID++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
s.items[sessionOwner] = newItems
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryService) GetByID(id int64) (Item, bool) {
|
|
||||||
item, found := s.items[id]
|
|
||||||
return item, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryService) GetByOwner(owner string) (items []Item) {
|
|
||||||
for _, item := range s.items {
|
|
||||||
if item.OwnerID != owner {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryService) Complete(item Item) bool {
|
|
||||||
item.CurrentState = StateCompleted
|
|
||||||
return s.Save(item) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryService) Save(newItem Item) error {
|
|
||||||
if newItem.ID == 0 {
|
|
||||||
// create
|
|
||||||
newItem.ID = s.getLatestID() + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// full replace here for the shake of simplicity)
|
|
||||||
s.items[newItem.ID] = newItem
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
|
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
"github.com/kataras/iris/sessions"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/mvc"
|
"github.com/kataras/iris/mvc"
|
||||||
|
"github.com/kataras/iris/sessions"
|
||||||
|
"github.com/kataras/iris/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TodoController is our TODO app's web controller.
|
// TodoController is our TODO app's web controller.
|
||||||
|
@ -23,55 +23,39 @@ type TodoController struct {
|
||||||
func (c *TodoController) BeforeActivation(b mvc.BeforeActivation) {
|
func (c *TodoController) BeforeActivation(b mvc.BeforeActivation) {
|
||||||
// this could be binded to a controller's function input argument
|
// this could be binded to a controller's function input argument
|
||||||
// if any, or struct field if any:
|
// if any, or struct field if any:
|
||||||
b.Dependencies().Add(func(ctx iris.Context) todo.Item {
|
b.Dependencies().Add(func(ctx iris.Context) (items []todo.Item) {
|
||||||
// ctx.ReadForm(&item)
|
ctx.ReadJSON(&items)
|
||||||
var (
|
return
|
||||||
owner = ctx.PostValue("owner")
|
})
|
||||||
body = ctx.PostValue("body")
|
}
|
||||||
state = ctx.PostValue("state")
|
|
||||||
)
|
|
||||||
|
|
||||||
return todo.Item{
|
// Get handles the GET: /todos route.
|
||||||
OwnerID: owner,
|
func (c *TodoController) Get() []todo.Item {
|
||||||
Body: body,
|
return c.Service.Get(c.Session.ID())
|
||||||
CurrentState: todo.ParseState(state),
|
}
|
||||||
}
|
|
||||||
|
// PostItemResponse the response data that will be returned as json
|
||||||
|
// after a post save action of all todo items.
|
||||||
|
type PostItemResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyResponse = PostItemResponse{Success: false}
|
||||||
|
|
||||||
|
// Post handles the POST: /todos route.
|
||||||
|
func (c *TodoController) Post(newItems []todo.Item) PostItemResponse {
|
||||||
|
if err := c.Service.Save(c.Session.ID(), newItems); err != nil {
|
||||||
|
return emptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return PostItemResponse{Success: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TodoController) GetSync(conn websocket.Connection) {
|
||||||
|
conn.Join(c.Session.ID())
|
||||||
|
conn.On("save", func() { // "save" event from client.
|
||||||
|
conn.To(c.Session.ID()).Emit("saved", nil) // fire a "save" event to the rest of the clients.
|
||||||
})
|
})
|
||||||
|
|
||||||
// ca.Router.Use(...).Done(...).Layout(...)
|
conn.Wait()
|
||||||
}
|
|
||||||
|
|
||||||
// Get handles the GET: /todo route.
|
|
||||||
func (c *TodoController) Get() []todo.Item {
|
|
||||||
return c.Service.GetByOwner(c.Session.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutCompleteBy handles the PUT: /todo/complete/{id:long} route.
|
|
||||||
func (c *TodoController) PutCompleteBy(id int64) int {
|
|
||||||
item, found := c.Service.GetByID(id)
|
|
||||||
if !found {
|
|
||||||
return iris.StatusNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.OwnerID != c.Session.ID() {
|
|
||||||
return iris.StatusForbidden
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Service.Complete(item) {
|
|
||||||
return iris.StatusBadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
return iris.StatusOK
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post handles the POST: /todo route.
|
|
||||||
func (c *TodoController) Post(newItem todo.Item) int {
|
|
||||||
if newItem.OwnerID != c.Session.ID() {
|
|
||||||
return iris.StatusForbidden
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Service.Save(newItem); err != nil {
|
|
||||||
return iris.StatusBadRequest
|
|
||||||
}
|
|
||||||
return iris.StatusOK
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,14 @@ import (
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
"github.com/kataras/iris/sessions"
|
"github.com/kataras/iris/sessions"
|
||||||
|
"github.com/kataras/iris/websocket"
|
||||||
|
|
||||||
"github.com/kataras/iris/mvc"
|
"github.com/kataras/iris/mvc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
// serve our app in public, public folder
|
// serve our app in public, public folder
|
||||||
// contains the client-side vue.js application,
|
// contains the client-side vue.js application,
|
||||||
// no need for any server-side template here,
|
// no need for any server-side template here,
|
||||||
|
@ -20,20 +22,28 @@ func main() {
|
||||||
app.StaticWeb("/", "./public")
|
app.StaticWeb("/", "./public")
|
||||||
|
|
||||||
sess := sessions.New(sessions.Config{
|
sess := sessions.New(sessions.Config{
|
||||||
Cookie: "_iris_session",
|
Cookie: "iris_session",
|
||||||
})
|
})
|
||||||
|
|
||||||
m := mvc.New(app.Party("/todo"))
|
ws := websocket.New(websocket.Config{})
|
||||||
|
|
||||||
|
// create our mvc application targeted to /todos relative sub path.
|
||||||
|
m := mvc.New(app.Party("/todos"))
|
||||||
// any dependencies bindings here...
|
// any dependencies bindings here...
|
||||||
m.AddDependencies(
|
m.AddDependencies(
|
||||||
|
todo.NewMemoryService(),
|
||||||
mvc.Session(sess),
|
mvc.Session(sess),
|
||||||
new(todo.MemoryService),
|
ws.Upgrade,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// http://localhost:8080/iris-ws.js
|
||||||
|
// serve the javascript client library to communicate with
|
||||||
|
// the iris high level websocket event system.
|
||||||
|
m.Router.Any("/iris-ws.js", websocket.ClientHandler())
|
||||||
|
|
||||||
// controllers registration here...
|
// controllers registration here...
|
||||||
m.Register(new(controllers.TodoController))
|
m.Register(new(controllers.TodoController))
|
||||||
|
|
||||||
// start the web server at http://localhost:8080
|
// start the web server at http://localhost:8080
|
||||||
app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker, iris.WithOptimizations)
|
app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +1,71 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html data-framework="vue">
|
<html data-framework="vue">
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Iris + Vue.js • TodoMVC</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@2.0.4/index.css">
|
|
||||||
<!-- this needs to be loaded before guide's inline scripts -->
|
|
||||||
<script src="https://vuejs.org/js/vue.js"></script>
|
|
||||||
<script src="https://unpkg.com/director@1.2.8/build/director.js"></script>
|
|
||||||
<style>[v-cloak] { display: none; }</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<section class="todoapp">
|
|
||||||
<header class="header">
|
|
||||||
<h1>todos</h1>
|
|
||||||
<input class="new-todo"
|
|
||||||
autofocus autocomplete="off"
|
|
||||||
placeholder="What needs to be done?"
|
|
||||||
v-model="newTodo"
|
|
||||||
@keyup.enter="addTodo">
|
|
||||||
</header>
|
|
||||||
<section class="main" v-show="todos.length" v-cloak>
|
|
||||||
<input class="toggle-all" type="checkbox" v-model="allDone">
|
|
||||||
<ul class="todo-list">
|
|
||||||
<li v-for="todo in filteredTodos"
|
|
||||||
class="todo"
|
|
||||||
:key="todo.id"
|
|
||||||
:class="{ completed: todo.completed, editing: todo == editedTodo }">
|
|
||||||
<div class="view">
|
|
||||||
<input class="toggle" type="checkbox" v-model="todo.completed">
|
|
||||||
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
|
|
||||||
<button class="destroy" @click="removeTodo(todo)"></button>
|
|
||||||
</div>
|
|
||||||
<input class="edit" type="text"
|
|
||||||
v-model="todo.title"
|
|
||||||
v-todo-focus="todo == editedTodo"
|
|
||||||
@blur="doneEdit(todo)"
|
|
||||||
@keyup.enter="doneEdit(todo)"
|
|
||||||
@keyup.esc="cancelEdit(todo)">
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<footer class="footer" v-show="todos.length" v-cloak>
|
|
||||||
<span class="todo-count">
|
|
||||||
<strong>{{ remaining }}</strong> {{ remaining | pluralize }} left
|
|
||||||
</span>
|
|
||||||
<ul class="filters">
|
|
||||||
<li><a href="#/all" :class="{ selected: visibility == 'all' }">All</a></li>
|
|
||||||
<li><a href="#/active" :class="{ selected: visibility == 'active' }">Active</a></li>
|
|
||||||
<li><a href="#/completed" :class="{ selected: visibility == 'completed' }">Completed</a></li>
|
|
||||||
</ul>
|
|
||||||
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
|
|
||||||
Clear completed
|
|
||||||
</button>
|
|
||||||
</footer>
|
|
||||||
</section>
|
|
||||||
<footer class="info">
|
|
||||||
<p>Double-click to edit a todo</p>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script src="/js/app.js"></script>
|
<head>
|
||||||
</body>
|
<meta charset="utf-8">
|
||||||
|
<title>Iris + Vue.js • TodoMVC</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@2.0.4/index.css">
|
||||||
|
<!-- this needs to be loaded before guide's inline scripts -->
|
||||||
|
<script src="https://vuejs.org/js/vue.js"></script>
|
||||||
|
<!-- $http -->
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<!-- -->
|
||||||
|
<script src="https://unpkg.com/director@1.2.8/build/director.js"></script>
|
||||||
|
<!-- websocket sync between multiple tabs -->
|
||||||
|
<script src="/todos/iris-ws.js"></script>
|
||||||
|
<!-- -->
|
||||||
|
<style>
|
||||||
|
[v-cloak] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section class="todoapp">
|
||||||
|
<header class="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input class="new-todo" autofocus autocomplete="off" placeholder="What needs to be done?" v-model="newTodo" @keyup.enter="addTodo">
|
||||||
|
</header>
|
||||||
|
<section class="main" v-show="todos.length" v-cloak>
|
||||||
|
<input class="toggle-all" type="checkbox" v-model="allDone">
|
||||||
|
<ul class="todo-list">
|
||||||
|
<li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{ completed: todo.completed, editing: todo == editedTodo }">
|
||||||
|
<div class="view">
|
||||||
|
<input class="toggle" type="checkbox" v-model="todo.completed">
|
||||||
|
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
|
||||||
|
<button class="destroy" @click="removeTodo(todo)"></button>
|
||||||
|
</div>
|
||||||
|
<input class="edit" type="text" v-model="todo.title" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)"
|
||||||
|
@keyup.esc="cancelEdit(todo)">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<footer class="footer" v-show="todos.length" v-cloak>
|
||||||
|
<span class="todo-count">
|
||||||
|
<strong>{{ remaining }}</strong> {{ remaining | pluralize }} left
|
||||||
|
</span>
|
||||||
|
<ul class="filters">
|
||||||
|
<li>
|
||||||
|
<a href="#/all" :class="{ selected: visibility == 'all' }">All</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#/active" :class="{ selected: visibility == 'active' }">Active</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#/completed" :class="{ selected: visibility == 'completed' }">Completed</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
|
||||||
|
Clear completed
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
<footer class="info">
|
||||||
|
<p>Double-click to edit a todo</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="/js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,19 +1,42 @@
|
||||||
// Full spec-compliant TodoMVC with localStorage persistence
|
// Full spec-compliant TodoMVC with Iris
|
||||||
// and hash-based routing in ~120 effective lines of JavaScript.
|
// and hash-based routing in ~120 effective lines of JavaScript.
|
||||||
|
|
||||||
// localStorage persistence
|
// var socket = new Ws("ws://localhost:8080/todos/sync");
|
||||||
var STORAGE_KEY = 'todos-vuejs-2.0'
|
|
||||||
|
// socket.On("saved", function () {
|
||||||
|
// console.log("receive: on saved");
|
||||||
|
// todoStorage.fetch();
|
||||||
|
// });
|
||||||
|
|
||||||
|
var todos = [];
|
||||||
|
|
||||||
var todoStorage = {
|
var todoStorage = {
|
||||||
fetch: function () {
|
fetch: function () {
|
||||||
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
|
axios.get("/todos").then(response => {
|
||||||
todos.forEach(function (todo, index) {
|
if (response.data == null) {
|
||||||
todo.id = index
|
return;
|
||||||
})
|
}
|
||||||
todoStorage.uid = todos.length
|
for (var i = 0; i < response.data.length; i++) {
|
||||||
|
// if (todos.length <=i || todos[i] === null) {
|
||||||
|
// todos.push(response.data[i]);
|
||||||
|
// } else {
|
||||||
|
// todos[i] = response.data[i];
|
||||||
|
// }
|
||||||
|
todos.push(response.data[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return todos
|
return todos
|
||||||
},
|
},
|
||||||
save: function (todos) {
|
save: function (todos) {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
|
axios.post("/todos", JSON.stringify(todos)).then(response => {
|
||||||
|
if (!response.data.success) {
|
||||||
|
window.alert("saving had a failure");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// console.log("send: save");
|
||||||
|
// socket.Emit("save")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +67,18 @@ var app = new Vue({
|
||||||
visibility: 'all'
|
visibility: 'all'
|
||||||
},
|
},
|
||||||
|
|
||||||
// watch todos change for localStorage persistence
|
// watch todos change for persistence
|
||||||
watch: {
|
watch: {
|
||||||
todos: {
|
todos: {
|
||||||
handler: function (todos) {
|
handler: function (todos) {
|
||||||
todoStorage.save(todos)
|
// // saved by this client.
|
||||||
|
// if (todos[todos.length - 1].id === 0) {
|
||||||
|
// todoStorage.save(todos);
|
||||||
|
// } else {
|
||||||
|
// console.log("item cannot be saved, already exists.");
|
||||||
|
// console.log(todos[todos.length - 1]);
|
||||||
|
// }
|
||||||
|
todoStorage.save(todos);
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
|
@ -90,7 +120,7 @@ var app = new Vue({
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.todos.push({
|
this.todos.push({
|
||||||
id: todoStorage.uid++,
|
id: 0, // just for the client-side.
|
||||||
title: value,
|
title: value,
|
||||||
completed: false
|
completed: false
|
||||||
})
|
})
|
||||||
|
@ -140,7 +170,7 @@ var app = new Vue({
|
||||||
})
|
})
|
||||||
|
|
||||||
// handle routing
|
// handle routing
|
||||||
function onHashChange () {
|
function onHashChange() {
|
||||||
var visibility = window.location.hash.replace(/#\/?/, '')
|
var visibility = window.location.hash.replace(/#\/?/, '')
|
||||||
if (filters[visibility]) {
|
if (filters[visibility]) {
|
||||||
app.visibility = visibility
|
app.visibility = visibility
|
||||||
|
|
|
@ -11,6 +11,8 @@ type (
|
||||||
InputIndex int
|
InputIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FuncInjector keeps the data that are needed in order to do the binding injection
|
||||||
|
// as fast as possible and with the best possible and safest way.
|
||||||
FuncInjector struct {
|
FuncInjector struct {
|
||||||
// the original function, is being used
|
// the original function, is being used
|
||||||
// only the .Call, which is referring to the same function, always.
|
// only the .Call, which is referring to the same function, always.
|
||||||
|
@ -27,6 +29,10 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MakeFuncInjector returns a new func injector, which will be the object
|
||||||
|
// that the caller should use to bind input arguments of the "fn" function.
|
||||||
|
//
|
||||||
|
// The hijack and the goodFunc are optional, the "values" is the dependencies values.
|
||||||
func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *FuncInjector {
|
func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *FuncInjector {
|
||||||
typ := IndirectType(fn.Type())
|
typ := IndirectType(fn.Type())
|
||||||
s := &FuncInjector{
|
s := &FuncInjector{
|
||||||
|
@ -100,10 +106,14 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, v
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a debug trace text.
|
||||||
func (s *FuncInjector) String() string {
|
func (s *FuncInjector) String() string {
|
||||||
return s.trace
|
return s.trace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject accepts an already created slice of input arguments
|
||||||
|
// and fills them, the "ctx" is optional and it's used
|
||||||
|
// on the dependencies that depends on one or more input arguments, these are the "ctx".
|
||||||
func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
|
func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
|
||||||
args := *in
|
args := *in
|
||||||
for _, input := range s.inputs {
|
for _, input := range s.inputs {
|
||||||
|
@ -118,6 +128,12 @@ func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
|
||||||
*in = args
|
*in = args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call calls the "Inject" with a new slice of input arguments
|
||||||
|
// that are computed by the length of the input argument from the MakeFuncInjector's "fn" function.
|
||||||
|
//
|
||||||
|
// If the function needs a receiver, so
|
||||||
|
// the caller should be able to in[0] = receiver before injection,
|
||||||
|
// then the `Inject` method should be used instead.
|
||||||
func (s *FuncInjector) Call(ctx ...reflect.Value) []reflect.Value {
|
func (s *FuncInjector) Call(ctx ...reflect.Value) []reflect.Value {
|
||||||
in := make([]reflect.Value, s.Length, s.Length)
|
in := make([]reflect.Value, s.Length, s.Length)
|
||||||
s.Inject(&in, ctx...)
|
s.Inject(&in, ctx...)
|
||||||
|
|
|
@ -5,11 +5,17 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BindType is the type of a binded object/value, it's being used to
|
||||||
|
// check if the value is accessible after a function call with a "ctx" when needed ( Dynamic type)
|
||||||
|
// or it's just a struct value (a service | Static type).
|
||||||
type BindType uint32
|
type BindType uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Static BindType = iota // simple assignable value, a static value.
|
// Static is the simple assignable value, a static value.
|
||||||
Dynamic // dynamic value, depends on some input arguments from the caller.
|
Static BindType = iota
|
||||||
|
// Dynamic returns a value but it depends on some input arguments from the caller,
|
||||||
|
// on serve time.
|
||||||
|
Dynamic
|
||||||
)
|
)
|
||||||
|
|
||||||
func bindTypeString(typ BindType) string {
|
func bindTypeString(typ BindType) string {
|
||||||
|
@ -21,6 +27,9 @@ func bindTypeString(typ BindType) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindObject contains the dependency value's read-only information.
|
||||||
|
// FuncInjector and StructInjector keeps information about their
|
||||||
|
// input arguments/or fields, these properties contain a `BindObject` inside them.
|
||||||
type BindObject struct {
|
type BindObject struct {
|
||||||
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
|
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
|
||||||
Value reflect.Value
|
Value reflect.Value
|
||||||
|
@ -29,6 +38,11 @@ type BindObject struct {
|
||||||
ReturnValue func([]reflect.Value) reflect.Value
|
ReturnValue func([]reflect.Value) reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeBindObject accepts any "v" value, struct, pointer or a function
|
||||||
|
// and a type checker that is used to check if the fields (if "v.elem()" is struct)
|
||||||
|
// or the input arguments (if "v.elem()" is func)
|
||||||
|
// are valid to be included as the final object's dependencies, even if the caller added more
|
||||||
|
// the "di" is smart enough to select what each "v" needs and what not before serve time.
|
||||||
func MakeBindObject(v reflect.Value, goodFunc TypeChecker) (b BindObject, err error) {
|
func MakeBindObject(v reflect.Value, goodFunc TypeChecker) (b BindObject, err error) {
|
||||||
if IsFunc(v) {
|
if IsFunc(v) {
|
||||||
b.BindType = Dynamic
|
b.BindType = Dynamic
|
||||||
|
@ -93,10 +107,13 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Val
|
||||||
return bf, outTyp, nil
|
return bf, outTyp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAssignable checks if "to" type can be used as "b.Value/ReturnValue".
|
||||||
func (b *BindObject) IsAssignable(to reflect.Type) bool {
|
func (b *BindObject) IsAssignable(to reflect.Type) bool {
|
||||||
return equalTypes(b.Type, to)
|
return equalTypes(b.Type, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assign sets the values to a setter, "toSetter" contains the setter, so the caller
|
||||||
|
// can use it for multiple and different structs/functions as well.
|
||||||
func (b *BindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) {
|
func (b *BindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) {
|
||||||
if b.BindType == Dynamic {
|
if b.BindType == Dynamic {
|
||||||
toSetter(b.ReturnValue(ctx))
|
toSetter(b.ReturnValue(ctx))
|
||||||
|
|
|
@ -18,6 +18,8 @@ type (
|
||||||
FieldIndex []int
|
FieldIndex []int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StructInjector keeps the data that are needed in order to do the binding injection
|
||||||
|
// as fast as possible and with the best possible and safest way.
|
||||||
StructInjector struct {
|
StructInjector struct {
|
||||||
initRef reflect.Value
|
initRef reflect.Value
|
||||||
initRefAsSlice []reflect.Value // useful when the struct is passed on a func as input args via reflection.
|
initRefAsSlice []reflect.Value // useful when the struct is passed on a func as input args via reflection.
|
||||||
|
@ -42,6 +44,12 @@ func (s *StructInjector) countBindType(typ BindType) (n int) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeStructInjector returns a new struct injector, which will be the object
|
||||||
|
// that the caller should use to bind exported fields or
|
||||||
|
// embedded unexported fields that contain exported fields
|
||||||
|
// of the "v" struct value or pointer.
|
||||||
|
//
|
||||||
|
// The hijack and the goodFunc are optional, the "values" is the dependencies values.
|
||||||
func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector {
|
func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector {
|
||||||
s := &StructInjector{
|
s := &StructInjector{
|
||||||
initRef: v,
|
initRef: v,
|
||||||
|
@ -149,6 +157,7 @@ func (s *StructInjector) fillStruct() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a debug trace message.
|
||||||
func (s *StructInjector) String() (trace string) {
|
func (s *StructInjector) String() (trace string) {
|
||||||
for i, f := range s.fields {
|
for i, f := range s.fields {
|
||||||
elemField := s.elemType.FieldByIndex(f.FieldIndex)
|
elemField := s.elemType.FieldByIndex(f.FieldIndex)
|
||||||
|
|
|
@ -2,8 +2,11 @@ package di
|
||||||
|
|
||||||
import "reflect"
|
import "reflect"
|
||||||
|
|
||||||
|
// Values is a shortcut of []reflect.Value,
|
||||||
|
// it makes easier to remove and add dependencies.
|
||||||
type Values []reflect.Value
|
type Values []reflect.Value
|
||||||
|
|
||||||
|
// NewValues returns new empty (dependencies) values.
|
||||||
func NewValues() Values {
|
func NewValues() Values {
|
||||||
return Values{}
|
return Values{}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +33,7 @@ func (bv Values) CloneWithFieldsOf(s interface{}) Values {
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the current "bv" values slice.
|
||||||
func (bv Values) Len() int {
|
func (bv Values) Len() int {
|
||||||
return len(bv)
|
return len(bv)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +45,8 @@ func (bv *Values) Add(values ...interface{}) {
|
||||||
bv.AddValues(ValuesOf(values)...)
|
bv.AddValues(ValuesOf(values)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddValues same as `Add` but accepts reflect.Value dependencies instead of interface{}
|
||||||
|
// and appends them to the list if they pass some checks.
|
||||||
func (bv *Values) AddValues(values ...reflect.Value) {
|
func (bv *Values) AddValues(values ...reflect.Value) {
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
if !goodVal(v) {
|
if !goodVal(v) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user