mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 10:56:29 +01:00
Update to version 8.5.2 | Read HISTORY.md: https://github.com/kataras/iris/blob/master/HISTORY.md#th-12-october-2017--v852
https://github.com/kataras/iris/blob/master/HISTORY.md#th-12-october-2017--v852 Former-commit-id: 2501cf6066812c2aac158d8d6cd4e992a2b538f9
This commit is contained in:
parent
92bb47803f
commit
b0f8329768
20
HISTORY.md
20
HISTORY.md
|
@ -18,6 +18,26 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
|
||||||
|
|
||||||
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
|
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
|
||||||
|
|
||||||
|
# Th, 12 October 2017 | v8.5.2
|
||||||
|
|
||||||
|
This version is part of the [releases](https://github.com/kataras/iris/releases).
|
||||||
|
|
||||||
|
## MVC
|
||||||
|
|
||||||
|
Add `bool` as a supported return value, if false then skips everything else and fires 404 not found.
|
||||||
|
|
||||||
|
New example which covers the Service and Repository layers side-by-side with the MVC Architectural pattern, clean and simple: [_examples/mvc/overview](_examples/mvc/overview).
|
||||||
|
|
||||||
|
## Websocket
|
||||||
|
|
||||||
|
Fix(?) https://github.com/kataras/iris/issues/782 by @jerson with PR: https://github.com/kataras/iris/pull/783.
|
||||||
|
|
||||||
|
## Minor
|
||||||
|
|
||||||
|
Add some minor comments for the view/django's origin type getters-- as pushed at PR: [#765](https://github.com/kataras/iris/pull/765).
|
||||||
|
|
||||||
|
[sessions/sessiondb/badger](sessions/sessiondb/badger) vendored with: https://github.com/kataras/iris/commit/e7517ec79b45673e7cad353e52023ebd7237cf38.
|
||||||
|
|
||||||
# Tu, 10 October 2017 | v8.5.1
|
# Tu, 10 October 2017 | v8.5.1
|
||||||
|
|
||||||
## MVC
|
## MVC
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<!-- #  Iris -->
|
<!-- #  Iris -->
|
||||||
|
|
||||||
## 03, October 2017 | Iris User Experience Report
|
# 03, October 2017 | Iris User Experience Report
|
||||||
|
|
||||||
Be part of the **first** Iris User Experience Report by submitting a simple form, it won't take more than **5 minutes**.
|
Be part of the **first** Iris User Experience Report by submitting a simple form, it won't take more than **2 minutes**.
|
||||||
|
|
||||||
The form contains some questions that you may need to answer in order to learn more about you; learning more about you helps us to serve you with the best possible way!
|
The form contains some questions that you may need to answer in order to learn more about you; learning more about you helps us to serve you with the best possible way!
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ _Psst_, we've produced a small video about your feelings regrating to Iris! You
|
||||||
### 📑 Table Of Content
|
### 📑 Table Of Content
|
||||||
|
|
||||||
* [Installation](#-installation)
|
* [Installation](#-installation)
|
||||||
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#tu-10-october-2017--v851)
|
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#th-12-october-2017--v852)
|
||||||
* [Learn](#-learn)
|
* [Learn](#-learn)
|
||||||
* [Structuring](_examples/#structuring)
|
* [Structuring](_examples/#structuring)
|
||||||
* [HTTP Listening](_examples/#http-listening)
|
* [HTTP Listening](_examples/#http-listening)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
8.5.1:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-10-october-2017--v851
|
8.5.2:https://github.com/kataras/iris/blob/master/HISTORY.md#th-12-october-2017--v852
|
|
@ -26,7 +26,7 @@ Structuring depends on your own needs. We can't tell you how to design your own
|
||||||
- [Example 1](mvc/login)
|
- [Example 1](mvc/login)
|
||||||
- [Example 2](structuring/mvc)
|
- [Example 2](structuring/mvc)
|
||||||
- [Example 3](structuring/handler-based)
|
- [Example 3](structuring/handler-based)
|
||||||
- [Example 4](mvc/using-method-result)
|
- [Example 4](mvc/overview)
|
||||||
|
|
||||||
### HTTP Listening
|
### HTTP Listening
|
||||||
|
|
||||||
|
@ -204,11 +204,14 @@ If you're new to back-end web development read about the MVC architectural patte
|
||||||
|
|
||||||
Follow the examples below,
|
Follow the examples below,
|
||||||
|
|
||||||
|
- [Overview - Plus Repository and Service layers](mvc/overview) **NEW**
|
||||||
|
|
||||||
|
<!--
|
||||||
- [Hello world](mvc/hello-world/main.go)
|
- [Hello world](mvc/hello-world/main.go)
|
||||||
- [Session Controller](mvc/session-controller/main.go)
|
- [Session Controller](mvc/session-controller/main.go)
|
||||||
- [A simple but featured Controller with model and views](mvc/controller-with-model-and-view)
|
- [A simple but featured Controller with model and views](mvc/controller-with-model-and-view)
|
||||||
- [Login showcase](mvc/login/main.go) **NEW**
|
- [Login showcase](mvc/login/main.go)
|
||||||
- [Using Method Result (plus Service-oriented design)](mvc/using-method-result)
|
-->
|
||||||
|
|
||||||
### Subdomains
|
### Subdomains
|
||||||
|
|
||||||
|
|
1
_examples/mvc/overview/datamodels/README.md
Normal file
1
_examples/mvc/overview/datamodels/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Data Model Layer
|
18
_examples/mvc/overview/datamodels/movie.go
Normal file
18
_examples/mvc/overview/datamodels/movie.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// file: datamodels/movie.go
|
||||||
|
|
||||||
|
package datamodels
|
||||||
|
|
||||||
|
// Movie is our sample data structure.
|
||||||
|
// Keep note that the tags for public-use (for our web app)
|
||||||
|
// should be kept in other file like "web/viewmodels/movie.go"
|
||||||
|
// which could wrap by embedding the datamodels.Movie or
|
||||||
|
// declare new fields instead butwe will use this datamodel
|
||||||
|
// as the only one Movie model in our application,
|
||||||
|
// for the shake of simplicty.
|
||||||
|
type Movie struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Genre string `json:"genre"`
|
||||||
|
Poster string `json:"poster"`
|
||||||
|
}
|
1
_examples/mvc/overview/datasource/README.md
Normal file
1
_examples/mvc/overview/datasource/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Data Source / Data Store Layer
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
package datasource
|
package datasource
|
||||||
|
|
||||||
import "github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
import "github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||||
|
|
||||||
// Movies is our imaginary data source.
|
// Movies is our imaginary data source.
|
||||||
var Movies = map[int64]models.Movie{
|
var Movies = map[int64]datamodels.Movie{
|
||||||
1: {
|
1: {
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Name: "Casablanca",
|
Name: "Casablanca",
|
BIN
_examples/mvc/overview/folder_structure.png
Normal file
BIN
_examples/mvc/overview/folder_structure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -3,10 +3,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kataras/iris/_examples/mvc/using-method-result/controllers"
|
"github.com/kataras/iris/_examples/mvc/overview/datasource"
|
||||||
"github.com/kataras/iris/_examples/mvc/using-method-result/datasource"
|
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||||
"github.com/kataras/iris/_examples/mvc/using-method-result/middleware"
|
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||||
"github.com/kataras/iris/_examples/mvc/using-method-result/services"
|
"github.com/kataras/iris/_examples/mvc/overview/web/controllers"
|
||||||
|
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
)
|
)
|
||||||
|
@ -15,17 +16,19 @@ func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
// Load the template files.
|
// Load the template files.
|
||||||
app.RegisterView(iris.HTML("./views", ".html"))
|
app.RegisterView(iris.HTML("./web/views", ".html"))
|
||||||
|
|
||||||
// Register our controllers.
|
// Register our controllers.
|
||||||
app.Controller("/hello", new(controllers.HelloController))
|
app.Controller("/hello", new(controllers.HelloController))
|
||||||
|
|
||||||
// Create our movie service (memory), we will bind it to the movie controller.
|
// Create our movie repository with some (memory) data from the datasource.
|
||||||
service := services.NewMovieServiceFromMemory(datasource.Movies)
|
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),
|
app.Controller("/movies", new(controllers.MovieController),
|
||||||
// Bind the "service" to the MovieController's Service (interface) field.
|
// Bind the "movieService" to the MovieController's Service (interface) field.
|
||||||
service,
|
movieService,
|
||||||
// Add the basic authentication(admin:password) middleware
|
// Add the basic authentication(admin:password) middleware
|
||||||
// for the /movies based requests.
|
// for the /movies based requests.
|
||||||
middleware.BasicAuth)
|
middleware.BasicAuth)
|
||||||
|
@ -33,6 +36,7 @@ func main() {
|
||||||
// Start the web server at localhost:8080
|
// Start the web server at localhost:8080
|
||||||
// http://localhost:8080/hello
|
// http://localhost:8080/hello
|
||||||
// http://localhost:8080/hello/iris
|
// http://localhost:8080/hello/iris
|
||||||
|
// http://localhost:8080/movies
|
||||||
// http://localhost:8080/movies/1
|
// http://localhost:8080/movies/1
|
||||||
app.Run(
|
app.Run(
|
||||||
iris.Addr("localhost:8080"),
|
iris.Addr("localhost:8080"),
|
20
_examples/mvc/overview/models/README.md
Normal file
20
_examples/mvc/overview/models/README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Domain Models
|
||||||
|
|
||||||
|
There should be the domain/business-level models.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||||
|
|
||||||
|
type Movie struct {
|
||||||
|
datamodels.Movie
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Movie) Validate() (Movie, error) {
|
||||||
|
/* do some checks and return an error if that Movie is not valid */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
However, we will use the "datamodels" as the only one models package because
|
||||||
|
Movie structure we don't need any extra functionality or validation inside it.
|
3
_examples/mvc/overview/repositories/README.md
Normal file
3
_examples/mvc/overview/repositories/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Repositories
|
||||||
|
|
||||||
|
The package which has direct access to the "datasource" and can manipulate data directly.
|
175
_examples/mvc/overview/repositories/movie_repository.go
Normal file
175
_examples/mvc/overview/repositories/movie_repository.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
// file: repositories/movie_repository.go
|
||||||
|
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Query represents the visitor and action queries.
|
||||||
|
type Query func(datamodels.Movie) bool
|
||||||
|
|
||||||
|
// MovieRepository handles the basic operations of a movie entity/model.
|
||||||
|
// It's an interface in order to be testable, i.e a memory movie repository or
|
||||||
|
// a connected to an sql database.
|
||||||
|
type MovieRepository interface {
|
||||||
|
Exec(query Query, action Query, limit int, mode int) (ok bool)
|
||||||
|
|
||||||
|
Select(query Query) (movie datamodels.Movie, found bool)
|
||||||
|
SelectMany(query Query, limit int) (results []datamodels.Movie)
|
||||||
|
|
||||||
|
InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error)
|
||||||
|
Delete(query Query, limit int) (deleted bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMovieRepository returns a new movie memory-based repository,
|
||||||
|
// the one and only repository type in our example.
|
||||||
|
func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
|
||||||
|
return &movieMemoryRepository{source: source}
|
||||||
|
}
|
||||||
|
|
||||||
|
// movieMemoryRepository is a "MovieRepository"
|
||||||
|
// which manages the movies using the memory data source (map).
|
||||||
|
type movieMemoryRepository struct {
|
||||||
|
source map[int64]datamodels.Movie
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ReadOnlyMode will RLock(read) the data .
|
||||||
|
ReadOnlyMode = iota
|
||||||
|
// ReadWriteMode will Lock(read/write) the data.
|
||||||
|
ReadWriteMode
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
|
||||||
|
loops := 0
|
||||||
|
|
||||||
|
if mode == ReadOnlyMode {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
} else {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, movie := range r.source {
|
||||||
|
ok = query(movie)
|
||||||
|
if ok {
|
||||||
|
if action(movie) {
|
||||||
|
if actionLimit >= loops {
|
||||||
|
break // break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select receives a query function
|
||||||
|
// which is fired for every single movie model inside
|
||||||
|
// our imaginary data source.
|
||||||
|
// When that function returns true then it stops the iteration.
|
||||||
|
//
|
||||||
|
// It returns the query's return last known "found" value
|
||||||
|
// and the last known movie model
|
||||||
|
// to help callers to reduce the LOC.
|
||||||
|
//
|
||||||
|
// It's actually a simple but very clever prototype function
|
||||||
|
// I'm using everywhere since I firstly think of it,
|
||||||
|
// hope you'll find it very useful as well.
|
||||||
|
func (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) {
|
||||||
|
found = r.Exec(query, func(m datamodels.Movie) bool {
|
||||||
|
movie = m
|
||||||
|
return true
|
||||||
|
}, 1, ReadOnlyMode)
|
||||||
|
|
||||||
|
// set an empty datamodels.Movie if not found at all.
|
||||||
|
if !found {
|
||||||
|
movie = datamodels.Movie{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectMany same as Select but returns one or more datamodels.Movie as a slice.
|
||||||
|
// If limit <=0 then it returns everything.
|
||||||
|
func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) {
|
||||||
|
r.Exec(query, func(m datamodels.Movie) bool {
|
||||||
|
results = append(results, m)
|
||||||
|
return true
|
||||||
|
}, limit, ReadOnlyMode)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertOrUpdate adds or updates a movie to the (memory) storage.
|
||||||
|
//
|
||||||
|
// Returns the new movie and an error if any.
|
||||||
|
func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.Movie, error) {
|
||||||
|
id := movie.ID
|
||||||
|
|
||||||
|
if id == 0 { // Create new action
|
||||||
|
var lastID int64
|
||||||
|
// find the biggest ID in order to not have duplications
|
||||||
|
// in productions apps you can use a third-party
|
||||||
|
// library to generate a UUID as string.
|
||||||
|
r.mu.RLock()
|
||||||
|
for _, item := range r.source {
|
||||||
|
if item.ID > lastID {
|
||||||
|
lastID = item.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.mu.RUnlock()
|
||||||
|
|
||||||
|
id = lastID + 1
|
||||||
|
movie.ID = id
|
||||||
|
|
||||||
|
// map-specific thing
|
||||||
|
r.mu.Lock()
|
||||||
|
r.source[id] = movie
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
return movie, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update action based on the movie.ID,
|
||||||
|
// here we will allow updating the poster and genre if not empty.
|
||||||
|
// Alternatively we could do pure replace instead:
|
||||||
|
// r.source[id] = movie
|
||||||
|
// and comment the code below;
|
||||||
|
current, exists := r.Select(func(m datamodels.Movie) bool {
|
||||||
|
return m.ID == id
|
||||||
|
})
|
||||||
|
|
||||||
|
if !exists { // ID is not a real one, return an error.
|
||||||
|
return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
|
||||||
|
}
|
||||||
|
|
||||||
|
// or comment these and r.source[id] = m for pure replace
|
||||||
|
if movie.Poster != "" {
|
||||||
|
current.Poster = movie.Poster
|
||||||
|
}
|
||||||
|
|
||||||
|
if movie.Genre != "" {
|
||||||
|
current.Genre = movie.Genre
|
||||||
|
}
|
||||||
|
|
||||||
|
// map-specific thing
|
||||||
|
r.mu.Lock()
|
||||||
|
r.source[id] = current
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
return movie, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
|
||||||
|
return r.Exec(query, func(m datamodels.Movie) bool {
|
||||||
|
delete(r.source, m.ID)
|
||||||
|
return true
|
||||||
|
}, limit, ReadWriteMode)
|
||||||
|
}
|
3
_examples/mvc/overview/services/README.md
Normal file
3
_examples/mvc/overview/services/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Service Layer
|
||||||
|
|
||||||
|
The package which has access to call functions from the "repositories" and "models" ("datamodels" only in that simple example). It should contain the domain logic.
|
65
_examples/mvc/overview/services/movie_service.go
Normal file
65
_examples/mvc/overview/services/movie_service.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// file: services/movie_service.go
|
||||||
|
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||||
|
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MovieService handles some of the CRUID operations of the movie datamodel.
|
||||||
|
// It depends on a movie repository for its actions.
|
||||||
|
// It's here to decouple the data source from the higher level compoments.
|
||||||
|
// As a result a different repository type can be used with the same logic without any aditional changes.
|
||||||
|
// It's an interface and it's used as interface everywhere
|
||||||
|
// because we may need to change or try an experimental different domain logic at the future.
|
||||||
|
type MovieService interface {
|
||||||
|
GetAll() []datamodels.Movie
|
||||||
|
GetByID(id int64) (datamodels.Movie, bool)
|
||||||
|
DeleteByID(id int64) bool
|
||||||
|
UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMovieService returns the default movie service.
|
||||||
|
func NewMovieService(repo repositories.MovieRepository) MovieService {
|
||||||
|
return &movieService{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type movieService struct {
|
||||||
|
repo repositories.MovieRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all movies.
|
||||||
|
func (s *movieService) GetAll() []datamodels.Movie {
|
||||||
|
return s.repo.SelectMany(func(_ datamodels.Movie) bool {
|
||||||
|
return true
|
||||||
|
}, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID returns a movie based on its id.
|
||||||
|
func (s *movieService) GetByID(id int64) (datamodels.Movie, bool) {
|
||||||
|
return s.repo.Select(func(m datamodels.Movie) bool {
|
||||||
|
return m.ID == id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePosterAndGenreByID updates a movie's poster and genre.
|
||||||
|
func (s *movieService) UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error) {
|
||||||
|
// update the movie and return it.
|
||||||
|
return s.repo.InsertOrUpdate(datamodels.Movie{
|
||||||
|
ID: id,
|
||||||
|
Poster: poster,
|
||||||
|
Genre: genre,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID deletes a movie by its id.
|
||||||
|
//
|
||||||
|
// Returns true if deleted otherwise false.
|
||||||
|
func (s *movieService) DeleteByID(id int64) bool {
|
||||||
|
return s.repo.Delete(func(m datamodels.Movie) bool {
|
||||||
|
return m.ID == id
|
||||||
|
}, 1)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// file: controllers/hello_controller.go
|
// file: web/controllers/hello_controller.go
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// file: controllers/movie_controller.go
|
// file: web/controllers/movie_controller.go
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||||
"github.com/kataras/iris/_examples/mvc/using-method-result/services"
|
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
"github.com/kataras/iris/mvc"
|
"github.com/kataras/iris/mvc"
|
||||||
|
@ -28,26 +28,36 @@ type MovieController struct {
|
||||||
// Get returns list of the movies.
|
// Get returns list of the movies.
|
||||||
// Demo:
|
// Demo:
|
||||||
// curl -i http://localhost:8080/movies
|
// curl -i http://localhost:8080/movies
|
||||||
func (c *MovieController) Get() []models.Movie {
|
//
|
||||||
|
// The correct way if you have sensitive data:
|
||||||
|
// func (c *MovieController) Get() (results []viewmodels.Movie) {
|
||||||
|
// data := c.Service.GetAll()
|
||||||
|
//
|
||||||
|
// for _, movie := range data {
|
||||||
|
// results = append(results, viewmodels.Movie{movie})
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// otherwise just return the datamodels.
|
||||||
|
func (c *MovieController) Get() (results []datamodels.Movie) {
|
||||||
return c.Service.GetAll()
|
return c.Service.GetAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBy returns a movie.
|
// GetBy returns a movie.
|
||||||
// Demo:
|
// Demo:
|
||||||
// curl -i http://localhost:8080/movies/1
|
// curl -i http://localhost:8080/movies/1
|
||||||
func (c *MovieController) GetBy(id int64) models.Movie {
|
func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
|
||||||
m, _ := c.Service.GetByID(id)
|
return c.Service.GetByID(id) // it will throw 404 if not found.
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutBy updates a movie.
|
// PutBy updates a movie.
|
||||||
// Demo:
|
// Demo:
|
||||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
// 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) (models.Movie, error) {
|
func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
|
||||||
// get the request data for poster and genre
|
// get the request data for poster and genre
|
||||||
file, info, err := c.Ctx.FormFile("poster")
|
file, info, err := c.Ctx.FormFile("poster")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return models.Movie{}, errors.New("failed due form file 'poster' missing")
|
return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
|
||||||
}
|
}
|
||||||
// we don't need the file so close it now.
|
// we don't need the file so close it now.
|
||||||
file.Close()
|
file.Close()
|
||||||
|
@ -56,12 +66,7 @@ func (c *MovieController) PutBy(id int64) (models.Movie, error) {
|
||||||
poster := info.Filename
|
poster := info.Filename
|
||||||
genre := c.Ctx.FormValue("genre")
|
genre := c.Ctx.FormValue("genre")
|
||||||
|
|
||||||
// update the movie and return it.
|
return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
|
||||||
return c.Service.InsertOrUpdate(models.Movie{
|
|
||||||
ID: id,
|
|
||||||
Poster: poster,
|
|
||||||
Genre: genre,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBy deletes a movie.
|
// DeleteBy deletes a movie.
|
|
@ -1,4 +1,4 @@
|
||||||
// file: middleware/basicauth.go
|
// file: web/middleware/basicauth.go
|
||||||
|
|
||||||
package middleware
|
package middleware
|
||||||
|
|
55
_examples/mvc/overview/web/viewmodels/README.md
Normal file
55
_examples/mvc/overview/web/viewmodels/README.md
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# View Models
|
||||||
|
|
||||||
|
There should be the view models, the structure that the client will be able to see.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Movie struct {
|
||||||
|
datamodels.Movie
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Movie) IsValid() bool {
|
||||||
|
/* do some checks and return true if it's valid... */
|
||||||
|
return m.ID > 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
|
||||||
|
so theoritically, something like the following is permitted if it's really necessary;
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Dispatch completes the `kataras/iris/mvc#Result` interface.
|
||||||
|
// Sends a `Movie` as a controlled http response.
|
||||||
|
// If its ID is zero or less then it returns a 404 not found error
|
||||||
|
// else it returns its json representation,
|
||||||
|
// (just like the controller's functions do for custom types by default).
|
||||||
|
//
|
||||||
|
// Don't overdo it, the application's logic should not be here.
|
||||||
|
// It's just one more step of validation before the response,
|
||||||
|
// simple checks can be added here.
|
||||||
|
//
|
||||||
|
// It's just a showcase,
|
||||||
|
// imagine the potentials this feature gives when designing a bigger application.
|
||||||
|
//
|
||||||
|
// This is called where the return value from a controller's method functions
|
||||||
|
// is type of `Movie`.
|
||||||
|
// For example the `controllers/movie_controller.go#GetBy`.
|
||||||
|
func (m Movie) Dispatch(ctx context.Context) {
|
||||||
|
if !m.IsValid() {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(m, context.JSON{Indent: " "})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
However, we will use the "datamodels" as the only one models package because
|
||||||
|
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
|
||||||
|
and we don't need any extra functionality or validation inside it.
|
|
@ -1,4 +1,4 @@
|
||||||
<!-- file: views/hello/index.html -->
|
<!-- file: web/views/hello/index.html -->
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
|
@ -1,4 +1,4 @@
|
||||||
<!-- file: views/hello/name.html -->
|
<!-- file: web/views/hello/name.html -->
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
|
@ -1,41 +0,0 @@
|
||||||
// file: models/movie.go
|
|
||||||
|
|
||||||
package models
|
|
||||||
|
|
||||||
import "github.com/kataras/iris/context"
|
|
||||||
|
|
||||||
// Movie is our sample data structure.
|
|
||||||
type Movie struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Year int `json:"year"`
|
|
||||||
Genre string `json:"genre"`
|
|
||||||
Poster string `json:"poster"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch completes the `kataras/iris/mvc#Result` interface.
|
|
||||||
// Sends a `Movie` as a controlled http response.
|
|
||||||
// If its ID is zero or less then it returns a 404 not found error
|
|
||||||
// else it returns its json representation,
|
|
||||||
// (just like the controller's functions do for custom types by default).
|
|
||||||
//
|
|
||||||
// Don't overdo it, the application's logic should not be here.
|
|
||||||
// It's just one more step of validation before the response,
|
|
||||||
// simple checks can be added here.
|
|
||||||
//
|
|
||||||
// It's just a showcase,
|
|
||||||
// imagine the potentials this feature gives when designing a bigger application.
|
|
||||||
//
|
|
||||||
// This is called where the return value from a controller's method functions
|
|
||||||
// is type of `Movie`.
|
|
||||||
// For example the `controllers/movie_controller.go#GetBy`.
|
|
||||||
func (m Movie) Dispatch(ctx context.Context) {
|
|
||||||
if m.ID <= 0 {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.JSON(m, context.JSON{Indent: " "})
|
|
||||||
}
|
|
||||||
|
|
||||||
// For those who wonder `iris.Context`(go 1.9 type alias feature) and
|
|
||||||
// `context.Context` is the same exact thing.
|
|
|
@ -1,206 +0,0 @@
|
||||||
// file: services/movie_service.go
|
|
||||||
|
|
||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MovieService handles CRUID operations of a movie entity/model.
|
|
||||||
// It's here to decouple the data source from the higher level compoments.
|
|
||||||
// As a result a different service for a specific datasource (or repository)
|
|
||||||
// can be used from the main application without any additional changes.
|
|
||||||
type MovieService interface {
|
|
||||||
GetSingle(query func(models.Movie) bool) (movie models.Movie, found bool)
|
|
||||||
GetByID(id int64) (models.Movie, bool)
|
|
||||||
|
|
||||||
InsertOrUpdate(movie models.Movie) (models.Movie, error)
|
|
||||||
DeleteByID(id int64) bool
|
|
||||||
|
|
||||||
GetMany(query func(models.Movie) bool, limit int) (result []models.Movie)
|
|
||||||
GetAll() []models.Movie
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMovieServiceFromMemory returns a new memory-based movie service.
|
|
||||||
func NewMovieServiceFromMemory(source map[int64]models.Movie) MovieService {
|
|
||||||
return &MovieMemoryService{
|
|
||||||
source: source,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Movie Service can have different data sources:
|
|
||||||
// func NewMovieServiceFromDB(db datasource.MySQL) {
|
|
||||||
// return &MovieDatabaseService{
|
|
||||||
// db: db,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Another pattern is to initialize the database connection
|
|
||||||
// or any source here based on a "string" name or an "enum".
|
|
||||||
// func NewMovieService(source string) MovieService {
|
|
||||||
// if source == "memory" {
|
|
||||||
// return NewMovieServiceFromMemory(datasource.Movies)
|
|
||||||
// }
|
|
||||||
// if source == "database" {
|
|
||||||
// db = datasource.NewDB("....")
|
|
||||||
// return NewMovieServiceFromDB(db)
|
|
||||||
// }
|
|
||||||
// [...]
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// MovieMemoryService is a "MovieService"
|
|
||||||
// which manages the movies using the memory data source (map).
|
|
||||||
type MovieMemoryService struct {
|
|
||||||
source map[int64]models.Movie
|
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSingle receives a query function
|
|
||||||
// which is fired for every single movie model inside
|
|
||||||
// our imaginary data source.
|
|
||||||
// When that function returns true then it stops the iteration.
|
|
||||||
//
|
|
||||||
// It returns the query's return last known boolean value
|
|
||||||
// and the last known movie model
|
|
||||||
// to help callers to reduce the LOC.
|
|
||||||
//
|
|
||||||
// It's actually a simple but very clever prototype function
|
|
||||||
// I'm using everywhere since I firstly think of it,
|
|
||||||
// hope you'll find it very useful as well.
|
|
||||||
func (s *MovieMemoryService) GetSingle(query func(models.Movie) bool) (movie models.Movie, found bool) {
|
|
||||||
s.mu.RLock()
|
|
||||||
for _, movie = range s.source {
|
|
||||||
found = query(movie)
|
|
||||||
if found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.mu.RUnlock()
|
|
||||||
|
|
||||||
// set an empty models.Movie if not found at all.
|
|
||||||
if !found {
|
|
||||||
movie = models.Movie{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByID returns a movie based on its id.
|
|
||||||
// Returns true if found, otherwise false, the bool should be always checked
|
|
||||||
// because the models.Movie may be filled with the latest element
|
|
||||||
// but not the correct one, although it can be used for debugging.
|
|
||||||
func (s *MovieMemoryService) GetByID(id int64) (models.Movie, bool) {
|
|
||||||
return s.GetSingle(func(m models.Movie) bool {
|
|
||||||
return m.ID == id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertOrUpdate adds or updates a movie to the (memory) storage.
|
|
||||||
//
|
|
||||||
// Returns the new movie and an error if any.
|
|
||||||
func (s *MovieMemoryService) InsertOrUpdate(movie models.Movie) (models.Movie, error) {
|
|
||||||
id := movie.ID
|
|
||||||
|
|
||||||
if id == 0 { // Create new action
|
|
||||||
var lastID int64
|
|
||||||
// find the biggest ID in order to not have duplications
|
|
||||||
// in productions apps you can use a third-party
|
|
||||||
// library to generate a UUID as string.
|
|
||||||
s.mu.RLock()
|
|
||||||
for _, item := range s.source {
|
|
||||||
if item.ID > lastID {
|
|
||||||
lastID = item.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.mu.RUnlock()
|
|
||||||
|
|
||||||
id = lastID + 1
|
|
||||||
movie.ID = id
|
|
||||||
|
|
||||||
// map-specific thing
|
|
||||||
s.mu.Lock()
|
|
||||||
s.source[id] = movie
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
return movie, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update action based on the movie.ID,
|
|
||||||
// here we will allow updating the poster and genre if not empty.
|
|
||||||
// Alternatively we could do pure replace instead:
|
|
||||||
// s.source[id] = movie
|
|
||||||
// and comment the code below;
|
|
||||||
current, exists := s.GetByID(id)
|
|
||||||
if !exists { // ID is not a real one, return an error.
|
|
||||||
return models.Movie{}, errors.New("failed to update a nonexistent movie")
|
|
||||||
}
|
|
||||||
|
|
||||||
// or comment these and s.source[id] = m for pure replace
|
|
||||||
if movie.Poster != "" {
|
|
||||||
current.Poster = movie.Poster
|
|
||||||
}
|
|
||||||
|
|
||||||
if movie.Genre != "" {
|
|
||||||
current.Genre = movie.Genre
|
|
||||||
}
|
|
||||||
|
|
||||||
// map-specific thing
|
|
||||||
s.mu.Lock()
|
|
||||||
s.source[id] = current
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
return movie, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteByID deletes a movie by its id.
|
|
||||||
//
|
|
||||||
// Returns true if deleted otherwise false.
|
|
||||||
func (s *MovieMemoryService) DeleteByID(id int64) bool {
|
|
||||||
if _, exists := s.GetByID(id); !exists {
|
|
||||||
// we could do _, exists := s.source[id] instead
|
|
||||||
// but we don't because you should learn
|
|
||||||
// how you can use that service's functions
|
|
||||||
// with any other source, i.e database.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// map-specific thing
|
|
||||||
s.mu.Lock()
|
|
||||||
delete(s.source, id)
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMany same as GetSingle but returns one or more models.Movie as a slice.
|
|
||||||
// If limit <=0 then it returns everything.
|
|
||||||
func (s *MovieMemoryService) GetMany(query func(models.Movie) bool, limit int) (result []models.Movie) {
|
|
||||||
loops := 0
|
|
||||||
|
|
||||||
s.mu.RLock()
|
|
||||||
for _, movie := range s.source {
|
|
||||||
loops++
|
|
||||||
|
|
||||||
passed := query(movie)
|
|
||||||
if passed {
|
|
||||||
result = append(result, movie)
|
|
||||||
}
|
|
||||||
// we have to return at least one movie if "passed" was true.
|
|
||||||
if limit >= loops {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.mu.RUnlock()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll returns all movies.
|
|
||||||
func (s *MovieMemoryService) GetAll() []models.Movie {
|
|
||||||
movies := s.GetMany(func(m models.Movie) bool { return true }, -1)
|
|
||||||
return movies
|
|
||||||
}
|
|
|
@ -110,11 +110,11 @@ func (f *uploadedFiles) scan(dir string) {
|
||||||
|
|
||||||
// add the file's Name and Size to the uploadedFiles memory list
|
// add the file's Name and Size to the uploadedFiles memory list
|
||||||
func (f *uploadedFiles) add(name string, size int64) uploadedFile {
|
func (f *uploadedFiles) add(name string, size int64) uploadedFile {
|
||||||
f.mu.Lock()
|
|
||||||
uf := uploadedFile{
|
uf := uploadedFile{
|
||||||
Name: name,
|
Name: name,
|
||||||
Size: size,
|
Size: size,
|
||||||
}
|
}
|
||||||
|
f.mu.Lock()
|
||||||
f.items = append(f.items, uf)
|
f.items = append(f.items, uf)
|
||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
|
|
||||||
|
|
|
@ -66,11 +66,12 @@ func (f *uploadedFiles) scan(dir string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *uploadedFiles) add(name string, size int64) uploadedFile {
|
func (f *uploadedFiles) add(name string, size int64) uploadedFile {
|
||||||
f.mu.Lock()
|
|
||||||
uf := uploadedFile{
|
uf := uploadedFile{
|
||||||
Name: name,
|
Name: name,
|
||||||
Size: size,
|
Size: size,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.mu.Lock()
|
||||||
f.items = append(f.items, uf)
|
f.items = append(f.items, uf)
|
||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
|
|
||||||
|
|
19
doc.go
19
doc.go
|
@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
|
||||||
|
|
||||||
Current Version
|
Current Version
|
||||||
|
|
||||||
8.5.1
|
8.5.2
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
|
|
||||||
|
@ -860,18 +860,23 @@ Response via output arguments, optionally, i.e
|
||||||
func(c *ExampleController) Get() string |
|
func(c *ExampleController) Get() string |
|
||||||
(string, string) |
|
(string, string) |
|
||||||
(string, int) |
|
(string, int) |
|
||||||
int |
|
|
||||||
(int, string |
|
|
||||||
(string, error) |
|
(string, error) |
|
||||||
|
int |
|
||||||
|
(int, string) |
|
||||||
|
(any, int) |
|
||||||
error |
|
error |
|
||||||
(int, error) |
|
(int, error) |
|
||||||
(customStruct, error) |
|
(customStruct, error) |
|
||||||
|
(any, error) |
|
||||||
|
bool |
|
||||||
|
(any, bool)
|
||||||
customStruct |
|
customStruct |
|
||||||
(customStruct, int) |
|
(customStruct, int) |
|
||||||
(customStruct, string) |
|
(customStruct, string) |
|
||||||
Result or (Result, error)
|
`Result` or (`Result`, error)
|
||||||
|
|
||||||
where Result is an interface which contains only that function: Dispatch(ctx iris.Context)
|
Where `any` means everything, from custom structs to standard language's types-.
|
||||||
|
`Result` is an interface which contains only that function: Dispatch(ctx iris.Context)
|
||||||
and Get where HTTP Method function(Post, Put, Delete...).
|
and Get where HTTP Method function(Post, Put, Delete...).
|
||||||
|
|
||||||
|
|
||||||
|
@ -883,11 +888,13 @@ and it will be sent to the client as expected.
|
||||||
* if `string` then it's the body.
|
* if `string` then it's the body.
|
||||||
* if `string` is the second output argument then it's the content type.
|
* if `string` is the second output argument then it's the content type.
|
||||||
* if `int` then it's the status code.
|
* if `int` then it's the status code.
|
||||||
|
* if `bool` is false then it throws 404 not found http error by skipping everything else.
|
||||||
* if `error` and not nil then (any type) response will be omitted and error's text with a 400 bad request will be rendered instead.
|
* if `error` and not nil then (any type) response will be omitted and error's text with a 400 bad request will be rendered instead.
|
||||||
* if `(int, error)` and error is not nil then the response result will be the error's text with the status code as `int`.
|
* if `(int, error)` and error is not nil then the response result will be the error's text with the status code as `int`.
|
||||||
* if `custom struct` or `interface{}` or `slice` or `map` then it will be rendered as json, unless a `string` content type is following.
|
* if `custom struct` or `interface{}` or `slice` or `map` then it will be rendered as json, unless a `string` content type is following.
|
||||||
* if `mvc.Result` then it executes its `Dispatch` function, so good design patters can be used to split the model's logic where needed.
|
* if `mvc.Result` then it executes its `Dispatch` function, so good design patters can be used to split the model's logic where needed.
|
||||||
|
|
||||||
|
|
||||||
The example below is not intended to be used in production but it's a good showcase of some of the return types we saw before;
|
The example below is not intended to be used in production but it's a good showcase of some of the return types we saw before;
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
@ -1014,7 +1021,7 @@ The example below is not intended to be used in production but it's a good showc
|
||||||
|
|
||||||
Another good example with a typical folder structure,
|
Another good example with a typical folder structure,
|
||||||
that many developers are used to work, can be found at:
|
that many developers are used to work, can be found at:
|
||||||
https://github.com/kataras/iris/tree/master/_examples/mvc/using-method-result.
|
https://github.com/kataras/iris/tree/master/_examples/mvc/overview.
|
||||||
|
|
||||||
|
|
||||||
Using Iris MVC for code reuse
|
Using Iris MVC for code reuse
|
||||||
|
|
2
iris.go
2
iris.go
|
@ -32,7 +32,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version is the current version number of the Iris Web Framework.
|
// Version is the current version number of the Iris Web Framework.
|
||||||
Version = "8.5.1"
|
Version = "8.5.2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP status codes as registered with IANA.
|
// HTTP status codes as registered with IANA.
|
||||||
|
|
|
@ -46,8 +46,8 @@ type (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrMissingControllerInstance is a static error which fired from `Controller` when
|
// ErrMissingControllerInstance is a static error which fired from `Controller` when
|
||||||
// the passed "c" instnace is not a valid type of `Controller`.
|
// the passed "c" instnace is not a valid type of `Controller` or `C`.
|
||||||
ErrMissingControllerInstance = errors.New("controller should have a field of Controller type")
|
ErrMissingControllerInstance = errors.New("controller should have a field of mvc.Controller or mvc.C type")
|
||||||
// ErrInvalidControllerType fired when the "Controller" field is not
|
// ErrInvalidControllerType fired when the "Controller" field is not
|
||||||
// the correct type.
|
// the correct type.
|
||||||
ErrInvalidControllerType = errors.New("controller instance is not a valid implementation")
|
ErrInvalidControllerType = errors.New("controller instance is not a valid implementation")
|
||||||
|
|
|
@ -40,7 +40,15 @@ func DispatchErr(ctx context.Context, status int, err error) {
|
||||||
// DispatchCommon is being used internally to send
|
// DispatchCommon is being used internally to send
|
||||||
// commonly used data to the response writer with a smart way.
|
// commonly used data to the response writer with a smart way.
|
||||||
func DispatchCommon(ctx context.Context,
|
func DispatchCommon(ctx context.Context,
|
||||||
statusCode int, contentType string, content []byte, v interface{}, err error) {
|
statusCode int, contentType string, content []byte, v interface{}, err error, found bool) {
|
||||||
|
|
||||||
|
// if we have a false boolean as a return value
|
||||||
|
// then skip everything and fire a not found,
|
||||||
|
// we even don't care about the given status code or the object or the content.
|
||||||
|
if !found {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
status := statusCode
|
status := statusCode
|
||||||
if status == 0 {
|
if status == 0 {
|
||||||
|
@ -99,16 +107,25 @@ func DispatchCommon(ctx context.Context,
|
||||||
// func(c *ExampleController) Get() string |
|
// func(c *ExampleController) Get() string |
|
||||||
// (string, string) |
|
// (string, string) |
|
||||||
// (string, int) |
|
// (string, int) |
|
||||||
|
// ...
|
||||||
// int |
|
// int |
|
||||||
// (int, string |
|
// (int, string |
|
||||||
// (string, error) |
|
// (string, error) |
|
||||||
|
// ...
|
||||||
// error |
|
// error |
|
||||||
// (int, error) |
|
// (int, error) |
|
||||||
// (customStruct, error) |
|
// (customStruct, error) |
|
||||||
|
// ...
|
||||||
|
// bool |
|
||||||
|
// (int, bool) |
|
||||||
|
// (string, bool) |
|
||||||
|
// (customStruct, bool) |
|
||||||
|
// ...
|
||||||
// customStruct |
|
// customStruct |
|
||||||
// (customStruct, int) |
|
// (customStruct, int) |
|
||||||
// (customStruct, string) |
|
// (customStruct, string) |
|
||||||
// Result or (Result, error)
|
// Result or (Result, error) and so on...
|
||||||
|
//
|
||||||
// where Get is an HTTP METHOD.
|
// where Get is an HTTP METHOD.
|
||||||
func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
|
func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
|
||||||
numOut := len(values)
|
numOut := len(values)
|
||||||
|
@ -117,11 +134,27 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
statusCode int
|
// if statusCode > 0 then send this status code.
|
||||||
|
// Except when err != nil then check if status code is < 400 and
|
||||||
|
// if it's set it as DefaultErrStatusCode.
|
||||||
|
// Except when found == false, then the status code is 404.
|
||||||
|
statusCode int
|
||||||
|
// if not empty then use that as content type,
|
||||||
|
// if empty and custom != nil then set it to application/json.
|
||||||
contentType string
|
contentType string
|
||||||
content []byte
|
// if len > 0 then write that to the response writer as raw bytes,
|
||||||
custom interface{}
|
// except when found == false or err != nil or custom != nil.
|
||||||
err error
|
content []byte
|
||||||
|
// if not nil then check
|
||||||
|
// for content type (or json default) and send the custom data object
|
||||||
|
// except when found == false or err != nil.
|
||||||
|
custom interface{}
|
||||||
|
// if not nil then check for its status code,
|
||||||
|
// if not status code or < 400 then set it as DefaultErrStatusCode
|
||||||
|
// and fire the error's text.
|
||||||
|
err error
|
||||||
|
// if false then skip everything and fire 404.
|
||||||
|
found = true // defaults to true of course, otherwise will break :)
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
|
@ -134,6 +167,16 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
|
||||||
|
|
||||||
f := v.Interface()
|
f := v.Interface()
|
||||||
|
|
||||||
|
if b, ok := f.(bool); ok {
|
||||||
|
found = b
|
||||||
|
if !found {
|
||||||
|
// skip everything, we don't care about other return values,
|
||||||
|
// this boolean is the heighest in order.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if i, ok := f.(int); ok {
|
if i, ok := f.(int); ok {
|
||||||
statusCode = i
|
statusCode = i
|
||||||
continue
|
continue
|
||||||
|
@ -183,5 +226,5 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchCommon(ctx, statusCode, contentType, content, custom, err)
|
DispatchCommon(ctx, statusCode, contentType, content, custom, err, found)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
// int |
|
// int |
|
||||||
// (int, string |
|
// (int, string |
|
||||||
// (string, error) |
|
// (string, error) |
|
||||||
|
// bool |
|
||||||
|
// (any, bool) |
|
||||||
// error |
|
// error |
|
||||||
// (int, error) |
|
// (int, error) |
|
||||||
// (customStruct, error) |
|
// (customStruct, error) |
|
||||||
|
@ -32,7 +34,7 @@ import (
|
||||||
//
|
//
|
||||||
// It completes the `activator.BaseController` interface.
|
// It completes the `activator.BaseController` interface.
|
||||||
//
|
//
|
||||||
// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/using-method-result/controllers.
|
// 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.
|
// Example usage at: https://github.com/kataras/iris/blob/master/mvc/method_result_test.go#L17.
|
||||||
type C struct {
|
type C struct {
|
||||||
// The Name of the `C` controller.
|
// The Name of the `C` controller.
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
// All types that complete this interface
|
// All types that complete this interface
|
||||||
// can be returned as values from the method functions.
|
// can be returned as values from the method functions.
|
||||||
//
|
//
|
||||||
// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/using-method-result.
|
// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/overview.
|
||||||
type Result interface { // NOTE: Should be always compatible with the methodfunc.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 should sends the response to the context's response writer.
|
||||||
Dispatch(ctx context.Context)
|
Dispatch(ctx context.Context)
|
||||||
|
|
|
@ -39,5 +39,5 @@ func (r Response) Dispatch(ctx context.Context) {
|
||||||
r.Content = []byte(s)
|
r.Content = []byte(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
methodfunc.DispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, r.Err)
|
methodfunc.DispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, r.Err, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
// wraps the template file name, layout, (any) view data, status code and error.
|
// 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.
|
// 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.
|
// Example at: https://github.com/kataras/iris/blob/master/_examples/mvc/overview/web/controllers/hello_controller.go.
|
||||||
type View struct {
|
type View struct {
|
||||||
Name string
|
Name string
|
||||||
Layout string
|
Layout string
|
||||||
|
|
Loading…
Reference in New Issue
Block a user