Skip to content

Repository files navigation

** 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

About

lightweight and fast HTTP router written in Go

Topics

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages