Skip to content

honmaple/forest

Repository files navigation

Example

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")
}

Route

Single parameter in path

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)

Multi parameters in path

// /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)

Named Route

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"

Server Static files

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))
})

Bind Params

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"})

Custom

Custom Middleware

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
})

Custom Logger

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
    ...
    })

Custom Error Handler

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())
}

Custom Context

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})
}

Custom Host Matcher

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

Custom URL Param

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)

About

lightweight and fast HTTP router written in Go

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages