# README
** Example #+begin_src go package main
import (
"net/http"
"github.com/honmaple/forest"
"github.com/honmaple/forest/middleware"
)
func main() {
r := forest.New(forest.Debug())
r.Use(middleware.Recover())
r.Use(middleware.Logger())
r.GET("/", func(c forest.Context) error {
return c.HTML(http.StatusOK, "<h1>Hello Forest</h1>")
})
v1 := r.Group(forest.WithPrefix("/api"))
{
v1.GET("/posts/{title}", func(c forest.Context) error {
return c.JSON(http.StatusOK, forest.H{"title": c.Param("title")})
})
v1.POST("/posts", func(c forest.Context) error {
type post struct {
Title string `json:"title" form:"title"`
Content string `json:"content" form:"content"`
}
p := post{}
if err := c.Bind(&p); err != nil {
return c.JSON(http.BadRequest, forest.H{"message": err.Error()})
}
return c.JSON(http.StatusOK, p)
})
}
v2 := forest.NewGroup(forest.WithHost("v2.localhost:8000"))
{
v2.GET("/posts/{title}", func(c forest.Context) error {
return c.JSON(http.StatusOK, forest.H{"title": c.Param("title")})
})
}
r.Mount(v2)
r.Start("127.0.0.1:8000")
}
#+end_src
** Route
*** Single parameter in path #+begin_src go router := forest.New() // /posts/1 {"var": "1"} // /posts/test {"var": "test"} // /posts, /posts/, /posts/1/1 not match router.GET("/posts/:var", handler) // /posts/ {"var": ""} // /posts/1 {"var": "1"} // /posts/test {"var": "test"} // /posts, /posts/1/1 not match router.GET("/posts/:var?", handler) // /posts/ {"var": ""} // /posts/1 {"var": "1"} // /posts/1/ {"var": "1/"} // /posts/1/test/2 {"var": "1/test/2"} router.GET("/posts/*var", handler) // /posts/1 {"var": "1"} // /posts/test {"var": "test"} // /posts, /posts/, /posts/1/1 not match router.GET("/posts/{var}", handler) router.GET("/posts/{var:string}", handler) // /posts/ {"var": ""} // /posts/1 {"var": "1"} // /posts/test {"var": "test"} // /posts, /posts/1/1 not match router.GET("/posts/{var?}", handler) // /posts/1 {"var": "1"} // /posts/test not match router.GET("/posts/{var:int}", handler) // /posts/1 not match // /posts/test not match // /posts/.1 {"var": ".1"} // /posts/1.1 {"var": "1.1"} // /posts/1.10 {"var": "1.10"} router.GET("/posts/{var:float}", handler) // /posts/1 {"var": "1"} // /posts/test {"var": "test"} // /posts/test/1 {"var": "test/1"} router.GET("/posts/{var:path}", handler) #+end_src *** Multi parameters in path #+begin_src go // /posts/1 not match // /posts/prefixtest {"var": "test"} router.GET("/posts/prefix:var", handler) // /posts/prefixtest {"var:end": "test"} router.GET("/posts/prefix:var:end", handler) // /posts/test not match // /posts/test/1 not match // /posts/test/1/test {"var": "test/1"} router.GET("/posts/*var/test", handler) // /posts/test not match // /posts/test-123 {"var": "test", "var1": "123"} router.GET("/posts/{var}-{var1:int}", handler) // /posts/123-test {"var": "123", "var1": "test"} router.GET("/posts/{var:int}-{var1}", handler) // /posts/123-test {"var": "123", "var1": "test"} // /posts/123/test-test {"var": "123/test", "var1": "test"} router.GET("/posts/{var:path}-{var1}", handler) // /posts/1/1/test {"var": "1", "var1": "1", "var2": "test"} // /posts/test/1/1/test {"var": "test/1", "var1": "1", "var2": "test"} // /posts/test/1/1/s/test not match router.GET("/posts/{var:path}/{var1:int}/{var2}", handler) // /posts/1/1/test {"var": "1", "var1": "1", "var2": "test"} // /posts/test/1/1/test {"var": "test", "var1": "1", "var2": "1/test"} // /posts/test/1/1/s/test {"var": "test", "var1": "1", "var2": "1/s/test"} router.GET("/posts/{var:path}/{var1:int}/{var2:path}", handler) #+end_src
*** Named Route #+begin_src go r := forest.New() g1 := r.Group(forest.WithPrefix("/api"), forest.WithName("g1")) g2 := g1.Group(forest.WithPrefix("/v1"), forest.WithName("g2")) r1 := api.GET("/posts").Named("list_posts", "some description") r2 := api.DELETE("/posts/:pk").Named("delete_post", "delete post with pk param") // result r.Route("g1.g2.list_posts") == r1 r.URL("g1.g2.list_posts") == r1.URL() == "/v1/api/posts" r.Route("g1.g2.delete_post") == r2 r.URL("g1.g2.delete_post", "12") == r2.URL("12") == "/v1/api/posts/12" #+end_src
*** Server Static files #+begin_src go r := forest.New() r.GET("/static/", func(c forest.Context) error { path := filepath.Join("static", c.Param("")) return c.FileFromFS(path, http.FS(staticFS)) }) r.GET("/robots.txt", func(c forest.Context) error { return c.FileFromFS("static/robots.txt", http.FS(staticFS)) }) r.GET("/favicon.ico", func(c forest.Context) error { return c.FileFromFS("static/favicon.ico", http.FS(staticFS)) }) #+end_src
*** Bind Params
#+begin_src go
type Params struct {
Text string query:"text" json:"text" form:"text" param:"text"
}
p := Params{}
// bind query, method: not POST, PUT, PATCH
// bind form or json or xml, method: POST, PUT, PATCH
c.Bind(&p)
// bind params, GET /test/:text
c.BindParams(&p)
// bind other params
c.BindWith(&p, bind.Query)
c.BindWith(&p, bind.Form)
c.BindWith(&p, bind.MultipartForm)
c.BindWith(&p, bind.JSON)
c.BindWith(&p, bind.XML)
c.BindWith(&p, bind.Params)
c.BindWith(&p, bind.Header)
// custom bind tag
c.BindWith(&p, bind.FormBinder{"json"})
c.BindWith(&p, bind.QueryBinder{"json"})
#+end_src
** Custom *** Custom Middleware #+begin_src go func MyMiddleware(c forest.Context) error { // do something // c.Next() is required, or else your handler will not execute return c.Next() } router := forest.New() // with root router.Use(MyMiddleware) // with group group := router.Group(forest.WithPrefix("/api/v1"), forest.WithMiddlewares(MyMiddleware)) // with special handler group.GET("/", MyMiddleware, func(c forest.Context) error { return nil }) #+end_src
*** Custom Logger #+begin_src go router := forest.New() router.Logger = Logger1
router.GET("/posts", func(c forest.Context) error {
// c.Logger() == Logger1
...
})
group := router.Group(forest.WithPrefix("/api/v1"))
group.GET("/posts", func(c forest.Context) error {
// c.Logger() == Logger1
...
})
group := router.Group(forest.WithPrefix("/api/v2"))
group.Logger = Logger2
group.GET("/posts", func(c forest.Context) error {
// c.Logger() == Logger2
...
})
#+end_src
*** Custom Error Handler #+begin_src go router := forest.New() // engine only router.NotFound(func(c forest.Context) error { return c.JSON(404, forest.H{"message": "not found"}) }) router.MethodNotAllowed(func(c forest.Context) error { return c.JSON(405, forest.H{"message": "method not allowed"}) })
router.ErrorHandler = func(err error, c Context) {
c.String(500, err.Error())
}
group := router.Group(forest.WithPrefix("/api/v1"))
// group only
group.ErrorHandler = func(err error, c Context) {
c.String(501, err.Error())
}
#+end_src
*** Custom Context #+begin_src go type MyContext struct { forest.Context }
func (c *MyContext) Next() error {
return c.NextWith(c)
}
func MyContextMiddleware(c forest.Context) error {
// doing somthing
return c.NextWith(&MyContext{c})
}
#+end_src
*** Custom Host Matcher #+begin_src go func matcher(host, dst string) bool { return host == dst } r := forest.New(forest.HostMatch(matcher)) // or use internal matcher r := forest.New(forest.HostMatch(forest.HostMatcher)) #+end_src
*** Custom URL Param #+begin_src go import ( "github.com/google/uuid" )
type UUIDMatcher struct {
}
func (s *UUIDMatcher) Name() string {
return "uuid"
}
func (s *UUIDMatcher) Match(path string, index int, next bool) (int, bool) {
if index > 0 {
return 0, false
}
if len(path) < 18 || (!next && len(path) > 18) {
return 0, false
}
_, err := uuid.Parse(path[:18])
if err != nil {
return 0, false
}
return 18, true
}
func NewUUIDMatcher(rule string) forest.Matcher {
return &UUIDMatcher{}
}
forest.RegisterRule("uuid", NewUUIDMatcher)
router := forest.New()
router.GET("/api/v1/user/{pk:uuid}", handler)
#+end_src