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") }
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)
// /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)
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"
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)) })
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"})
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 })
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 ... })
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()) }
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}) }
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))
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)