Browse Source

Comments + IRoutes + IRouter + unexported AbortIndex

Manu Mtz-Almeida 10 years ago
parent
commit
8f3047814e
8 changed files with 114 additions and 86 deletions
  1. 7 9
      auth.go
  2. 15 11
      context.go
  3. 4 4
      context_test.go
  4. 5 2
      debug.go
  5. 21 12
      gin.go
  6. 10 0
      response_writer.go
  7. 51 47
      routergroup.go
  8. 1 1
      routergroup_test.go

+ 7 - 9
auth.go

@@ -10,9 +10,7 @@ import (
 	"strconv"
 )
 
-const (
-	AuthUserKey = "user"
-)
+const AuthUserKey = "user"
 
 type (
 	Accounts map[string]string
@@ -35,8 +33,9 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
 	return "", false
 }
 
-// Implements a basic Basic HTTP Authorization. It takes as arguments a map[string]string where
-// the key is the user name and the value is the password, as well as the name of the Realm
+// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
+// the key is the user name and the value is the password, as well as the name of the Realm.
+// If the realm is empty, "Authorization Required" will be used by default.
 // (see http://tools.ietf.org/html/rfc2617#section-1.2)
 func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
 	if realm == "" {
@@ -59,7 +58,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
 	}
 }
 
-// Implements a basic Basic HTTP Authorization. It takes as argument a map[string]string where
+// BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
 // the key is the user name and the value is the password.
 func BasicAuth(accounts Accounts) HandlerFunc {
 	return BasicAuthForRealm(accounts, "")
@@ -91,8 +90,7 @@ func authorizationHeader(user, password string) string {
 func secureCompare(given, actual string) bool {
 	if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
 		return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
-	} else {
-		/* Securely compare actual to itself to keep constant time, but always return false */
-		return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
 	}
+	/* Securely compare actual to itself to keep constant time, but always return false */
+	return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
 }

+ 15 - 11
context.go

@@ -18,6 +18,7 @@ import (
 	"golang.org/x/net/context"
 )
 
+// Content-Type MIME of the most common data formats
 const (
 	MIMEJSON              = binding.MIMEJSON
 	MIMEHTML              = binding.MIMEHTML
@@ -28,7 +29,7 @@ const (
 	MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
 )
 
-const AbortIndex int8 = math.MaxInt8 / 2
+const abortIndex int8 = math.MaxInt8 / 2
 
 // Context is the most important part of gin. It allows us to pass variables between middleware,
 // manage the flow, validate the JSON of a request and render a JSON response for example.
@@ -63,16 +64,18 @@ func (c *Context) reset() {
 	c.Accepted = nil
 }
 
+// Copy returns a copy of the current context that can be safely used outside the request's scope.
+// This have to be used then the context has to be passed to a goroutine.
 func (c *Context) Copy() *Context {
 	var cp Context = *c
 	cp.writermem.ResponseWriter = nil
 	cp.Writer = &cp.writermem
-	cp.index = AbortIndex
+	cp.index = abortIndex
 	cp.handlers = nil
 	return &cp
 }
 
-// Returns the main handle's name. For example if the handler is "handleGetUsers()", this
+// HandlerName returns the main handle's name. For example if the handler is "handleGetUsers()", this
 // function will return "main.handleGetUsers"
 func (c *Context) HandlerName() string {
 	return nameOfFunction(c.handlers.Last())
@@ -93,27 +96,27 @@ func (c *Context) Next() {
 	}
 }
 
-// Returns if the currect context was aborted.
+// IsAborted returns true if the currect context was aborted.
 func (c *Context) IsAborted() bool {
-	return c.index >= AbortIndex
+	return c.index >= abortIndex
 }
 
-// Stops the system to continue calling the pending handlers in the chain.
+// Abort stops the system to continue calling the pending handlers in the chain.
 // Let's say you have an authorization middleware that validates if the request is authorized
 // if the authorization fails (the password does not match). This method (Abort()) should be called
 // in order to stop the execution of the actual handler.
 func (c *Context) Abort() {
-	c.index = AbortIndex
+	c.index = abortIndex
 }
 
-// It calls Abort() and writes the headers with the specified status code.
+// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.
 // For example, a failed attempt to authentificate a request could use: context.AbortWithStatus(401).
 func (c *Context) AbortWithStatus(code int) {
 	c.Writer.WriteHeader(code)
 	c.Abort()
 }
 
-// It calls AbortWithStatus() and Error() internally. This method stops the chain, writes the status code and
+// AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and
 // pushes the specified error to `c.Errors`.
 // See Context.Error() for more details.
 func (c *Context) AbortWithError(code int, err error) *Error {
@@ -371,7 +374,7 @@ func (c *Context) Redirect(code int, location string) {
 	})
 }
 
-// Writes some data into the body stream and updates the HTTP code.
+// Data writes some data into the body stream and updates the HTTP code.
 func (c *Context) Data(code int, contentType string, data []byte) {
 	c.Render(code, render.Data{
 		ContentType: contentType,
@@ -379,11 +382,12 @@ func (c *Context) Data(code int, contentType string, data []byte) {
 	})
 }
 
-// Writes the specified file into the body stream in a efficient way.
+// File writes the specified file into the body stream in a efficient way.
 func (c *Context) File(filepath string) {
 	http.ServeFile(c.Writer, c.Request, filepath)
 }
 
+// SSEvent writes a Server-Sent Event into the body stream.
 func (c *Context) SSEvent(name string, message interface{}) {
 	c.Render(-1, sse.Event{
 		Event: name,

+ 4 - 4
context_test.go

@@ -129,7 +129,7 @@ func TestContextCopy(t *testing.T) {
 	assert.Nil(t, cp.writermem.ResponseWriter)
 	assert.Equal(t, &cp.writermem, cp.Writer.(*responseWriter))
 	assert.Equal(t, cp.Request, c.Request)
-	assert.Equal(t, cp.index, AbortIndex)
+	assert.Equal(t, cp.index, abortIndex)
 	assert.Equal(t, cp.Keys, c.Keys)
 	assert.Equal(t, cp.engine, c.engine)
 	assert.Equal(t, cp.Params, c.Params)
@@ -418,7 +418,7 @@ func TestContextIsAborted(t *testing.T) {
 	c.Next()
 	assert.True(t, c.IsAborted())
 
-	c.Next()
+	c.index++
 	assert.True(t, c.IsAborted())
 }
 
@@ -430,7 +430,7 @@ func TestContextAbortWithStatus(t *testing.T) {
 	c.AbortWithStatus(401)
 	c.Writer.WriteHeaderNow()
 
-	assert.Equal(t, c.index, AbortIndex)
+	assert.Equal(t, c.index, abortIndex)
 	assert.Equal(t, c.Writer.Status(), 401)
 	assert.Equal(t, w.Code, 401)
 	assert.True(t, c.IsAborted())
@@ -482,7 +482,7 @@ func TestContextAbortWithError(t *testing.T) {
 	c.Writer.WriteHeaderNow()
 
 	assert.Equal(t, w.Code, 401)
-	assert.Equal(t, c.index, AbortIndex)
+	assert.Equal(t, c.index, abortIndex)
 	assert.True(t, c.IsAborted())
 }
 

+ 5 - 2
debug.go

@@ -9,6 +9,9 @@ import "log"
 func init() {
 	log.SetFlags(0)
 }
+
+// IsDebugging returns true if the framework is running in debug mode.
+// Use SetMode(gin.Release) to switch to disable the debug mode.
 func IsDebugging() bool {
 	return ginMode == debugCode
 }
@@ -27,7 +30,7 @@ func debugPrint(format string, values ...interface{}) {
 	}
 }
 
-func debugPrintWARNING_New() {
+func debugPrintWARNINGNew() {
 	debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
  - using env:	export GIN_MODE=release
  - using code:	gin.SetMode(gin.ReleaseMode)
@@ -35,7 +38,7 @@ func debugPrintWARNING_New() {
 `)
 }
 
-func debugPrintWARNING_SetHTMLTemplate() {
+func debugPrintWARNINGSetHTMLTemplate() {
 	debugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called
 at initialization. ie. before any route is registered or the router is listening in a socket:
 

+ 21 - 12
gin.go

@@ -14,6 +14,7 @@ import (
 	"github.com/gin-gonic/gin/render"
 )
 
+// Framework's version
 const Version = "v1.0rc2"
 
 var default404Body = []byte("404 page not found")
@@ -22,6 +23,7 @@ var default405Body = []byte("405 method not allowed")
 type HandlerFunc func(*Context)
 type HandlersChain []HandlerFunc
 
+// Last returns the last handler in the chain. ie. the last handler is the main own.
 func (c HandlersChain) Last() HandlerFunc {
 	length := len(c)
 	if length > 0 {
@@ -38,7 +40,8 @@ type (
 		Handler string
 	}
 
-	// Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares.
+	// Engine is the framework's instance, it contains the muxer, middlewares and configuration settings.
+	// Create an instance of Engine, by using New() or Default()
 	Engine struct {
 		RouterGroup
 		HTMLRender  render.HTMLRender
@@ -78,12 +81,16 @@ type (
 	}
 )
 
-var _ RoutesInterface = &Engine{}
+var _ IRouter = &Engine{}
 
-// Returns a new blank Engine instance without any middleware attached.
-// The most basic configuration
+// New returns a new blank Engine instance without any middleware attached.
+// By default the configuration is:
+// - RedirectTrailingSlash:  true
+// - RedirectFixedPath:      false
+// - HandleMethodNotAllowed: false
+// - ForwardedByClientIP:    true
 func New() *Engine {
-	debugPrintWARNING_New()
+	debugPrintWARNINGNew()
 	engine := &Engine{
 		RouterGroup: RouterGroup{
 			Handlers: nil,
@@ -103,7 +110,7 @@ func New() *Engine {
 	return engine
 }
 
-// Returns a Engine instance with the Logger and Recovery already attached.
+// Default returns an Engine instance with the Logger and Recovery middleware already attached.
 func Default() *Engine {
 	engine := New()
 	engine.Use(Recovery(), Logger())
@@ -134,7 +141,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
 
 func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
 	if len(engine.trees) > 0 {
-		debugPrintWARNING_SetHTMLTemplate()
+		debugPrintWARNINGSetHTMLTemplate()
 	}
 	engine.HTMLRender = render.HTMLProduction{Template: templ}
 }
@@ -154,7 +161,7 @@ func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
 // Attachs a global middleware to the router. ie. the middlewares attached though Use() will be
 // included in the handlers chain for every single request. Even 404, 405, static files...
 // For example, this is the right place for a logger or error management middleware.
-func (engine *Engine) Use(middlewares ...HandlerFunc) routesInterface {
+func (engine *Engine) Use(middlewares ...HandlerFunc) IRoutes {
 	engine.RouterGroup.Use(middlewares...)
 	engine.rebuild404Handlers()
 	engine.rebuild405Handlers()
@@ -193,6 +200,8 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
 	root.addRoute(path, handlers)
 }
 
+// Routes returns a slice of registered routes, including some useful information, such as:
+// the http method, path and the handler name.
 func (engine *Engine) Routes() (routes RoutesInfo) {
 	for _, tree := range engine.trees {
 		routes = iterate("", tree.method, routes, tree.root)
@@ -215,7 +224,7 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
 	return routes
 }
 
-// The router is attached to a http.Server and starts listening and serving HTTP requests.
+// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
 // It is a shortcut for http.ListenAndServe(addr, router)
 // Note: this method will block the calling goroutine undefinitelly unless an error happens.
 func (engine *Engine) Run(addr string) (err error) {
@@ -226,7 +235,7 @@ func (engine *Engine) Run(addr string) (err error) {
 	return
 }
 
-// The router is attached to a http.Server and starts listening and serving HTTPS requests.
+// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
 // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
 // Note: this method will block the calling goroutine undefinitelly unless an error happens.
 func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) {
@@ -237,8 +246,8 @@ func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err
 	return
 }
 
-// The router is attached to a http.Server and starts listening and serving HTTP requests
-// through the specified unix socket (ie. a file)
+// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
+// through the specified unix socket (ie. a file).
 // Note: this method will block the calling goroutine undefinitelly unless an error happens.
 func (engine *Engine) RunUnix(file string) (err error) {
 	debugPrint("Listening and serving HTTP on unix:/%s", file)

+ 10 - 0
response_writer.go

@@ -23,10 +23,20 @@ type (
 		http.Flusher
 		http.CloseNotifier
 
+		// Returns the HTTP response status code of the current request.
 		Status() int
+
+		// Returns the number of bytes already written into the response http body.
+		// See Written()
 		Size() int
+
+		// Writes the string into the response body.
 		WriteString(string) (int, error)
+
+		// Returns true if the response body was already written.
 		Written() bool
+
+		// Forces to write the http header (status code + headers).
 		WriteHeaderNow()
 	}
 

+ 51 - 47
routergroup.go

@@ -12,30 +12,30 @@ import (
 )
 
 type (
-	RoutesInterface interface {
-		routesInterface
+	IRouter interface {
+		IRoutes
 		Group(string, ...HandlerFunc) *RouterGroup
 	}
 
-	routesInterface interface {
-		Use(...HandlerFunc) routesInterface
-
-		Handle(string, string, ...HandlerFunc) routesInterface
-		Any(string, ...HandlerFunc) routesInterface
-		GET(string, ...HandlerFunc) routesInterface
-		POST(string, ...HandlerFunc) routesInterface
-		DELETE(string, ...HandlerFunc) routesInterface
-		PATCH(string, ...HandlerFunc) routesInterface
-		PUT(string, ...HandlerFunc) routesInterface
-		OPTIONS(string, ...HandlerFunc) routesInterface
-		HEAD(string, ...HandlerFunc) routesInterface
-
-		StaticFile(string, string) routesInterface
-		Static(string, string) routesInterface
-		StaticFS(string, http.FileSystem) routesInterface
+	IRoutes interface {
+		Use(...HandlerFunc) IRoutes
+
+		Handle(string, string, ...HandlerFunc) IRoutes
+		Any(string, ...HandlerFunc) IRoutes
+		GET(string, ...HandlerFunc) IRoutes
+		POST(string, ...HandlerFunc) IRoutes
+		DELETE(string, ...HandlerFunc) IRoutes
+		PATCH(string, ...HandlerFunc) IRoutes
+		PUT(string, ...HandlerFunc) IRoutes
+		OPTIONS(string, ...HandlerFunc) IRoutes
+		HEAD(string, ...HandlerFunc) IRoutes
+
+		StaticFile(string, string) IRoutes
+		Static(string, string) IRoutes
+		StaticFS(string, http.FileSystem) IRoutes
 	}
 
-	// Used internally to configure router, a RouterGroup is associated with a prefix
+	// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
 	// and an array of handlers (middlewares)
 	RouterGroup struct {
 		Handlers HandlersChain
@@ -45,15 +45,15 @@ type (
 	}
 )
 
-var _ RoutesInterface = &RouterGroup{}
+var _ IRouter = &RouterGroup{}
 
-// Adds middlewares to the group, see example code in github.
-func (group *RouterGroup) Use(middlewares ...HandlerFunc) routesInterface {
+// Use adds middlewares to the group, see example code in github.
+func (group *RouterGroup) Use(middlewares ...HandlerFunc) IRoutes {
 	group.Handlers = append(group.Handlers, middlewares...)
 	return group.returnObj()
 }
 
-// Creates a new router group. You should add all the routes that have common middlwares or the same path prefix.
+// Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix.
 // For example, all the routes that use a common middlware for authorization could be grouped.
 func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
 	return &RouterGroup{
@@ -63,6 +63,13 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R
 	}
 }
 
+func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
+	absolutePath := group.calculateAbsolutePath(relativePath)
+	handlers = group.combineHandlers(handlers)
+	group.engine.addRoute(httpMethod, absolutePath, handlers)
+	return group.returnObj()
+}
+
 // Handle registers a new request handle and middlewares with the given path and method.
 // The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes.
 // See the example code in github.
@@ -73,14 +80,7 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R
 // This function is intended for bulk loading and to allow the usage of less
 // frequently used, non-standardized or custom methods (e.g. for internal
 // communication with a proxy).
-func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) routesInterface {
-	absolutePath := group.calculateAbsolutePath(relativePath)
-	handlers = group.combineHandlers(handlers)
-	group.engine.addRoute(httpMethod, absolutePath, handlers)
-	return group.returnObj()
-}
-
-func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) routesInterface {
+func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
 	if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
 		panic("http method " + httpMethod + " is not valid")
 	}
@@ -88,42 +88,43 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha
 }
 
 // POST is a shortcut for router.Handle("POST", path, handle)
-func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) routesInterface {
+func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
 	return group.handle("POST", relativePath, handlers)
 }
 
 // GET is a shortcut for router.Handle("GET", path, handle)
-func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) routesInterface {
+func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
 	return group.handle("GET", relativePath, handlers)
 }
 
 // DELETE is a shortcut for router.Handle("DELETE", path, handle)
-func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) routesInterface {
+func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
 	return group.handle("DELETE", relativePath, handlers)
 }
 
 // PATCH is a shortcut for router.Handle("PATCH", path, handle)
-func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) routesInterface {
+func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
 	return group.handle("PATCH", relativePath, handlers)
 }
 
 // PUT is a shortcut for router.Handle("PUT", path, handle)
-func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) routesInterface {
+func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
 	return group.handle("PUT", relativePath, handlers)
 }
 
 // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
-func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) routesInterface {
+func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
 	return group.handle("OPTIONS", relativePath, handlers)
 }
 
 // HEAD is a shortcut for router.Handle("HEAD", path, handle)
-func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) routesInterface {
+func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
 	return group.handle("HEAD", relativePath, handlers)
 }
 
-func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) routesInterface {
-	// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE
+// Any registers a route that matches all the HTTP methods.
+// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE
+func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
 	group.handle("GET", relativePath, handlers)
 	group.handle("POST", relativePath, handlers)
 	group.handle("PUT", relativePath, handlers)
@@ -136,7 +137,9 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) rout
 	return group.returnObj()
 }
 
-func (group *RouterGroup) StaticFile(relativePath, filepath string) routesInterface {
+// StaticFile registers a single route in order to server a single file of the local filesystem.
+// router.StaticFile("favicon.ico", "./resources/favicon.ico")
+func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
 	if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
 		panic("URL parameters can not be used when serving a static file")
 	}
@@ -154,11 +157,13 @@ func (group *RouterGroup) StaticFile(relativePath, filepath string) routesInterf
 // To use the operating system's file system implementation,
 // use :
 //     router.Static("/static", "/var/www")
-func (group *RouterGroup) Static(relativePath, root string) routesInterface {
+func (group *RouterGroup) Static(relativePath, root string) IRoutes {
 	return group.StaticFS(relativePath, Dir(root, false))
 }
 
-func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) routesInterface {
+// StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead.
+// Gin by default user: gin.Dir()
+func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {
 	if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
 		panic("URL parameters can not be used when serving a static folder")
 	}
@@ -185,7 +190,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
 
 func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
 	finalSize := len(group.Handlers) + len(handlers)
-	if finalSize >= int(AbortIndex) {
+	if finalSize >= int(abortIndex) {
 		panic("too many handlers")
 	}
 	mergedHandlers := make(HandlersChain, finalSize)
@@ -198,10 +203,9 @@ func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
 	return joinPaths(group.BasePath, relativePath)
 }
 
-func (group *RouterGroup) returnObj() routesInterface {
+func (group *RouterGroup) returnObj() IRoutes {
 	if group.root {
 		return group.engine
-	} else {
-		return group
 	}
+	return group
 }

+ 1 - 1
routergroup_test.go

@@ -157,7 +157,7 @@ func TestRouterGroupPipeline(t *testing.T) {
 	testRoutesInterface(t, v1)
 }
 
-func testRoutesInterface(t *testing.T, r RoutesInterface) {
+func testRoutesInterface(t *testing.T, r IRoutes) {
 	handler := func(c *Context) {}
 	assert.Equal(t, r, r.Use(handler))