forest

package module
v0.0.0-...-bf8f869 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2022 License: MIT Imports: 17 Imported by: 7

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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound            = NewError(http.StatusNotFound)
	ErrMethodNotAllowed    = NewError(http.StatusMethodNotAllowed)
	ErrInternalServerError = NewError(http.StatusInternalServerError)

	NotFoundMessage         = []byte(ErrNotFound.Error())
	MethodNotAllowedMessage = []byte(ErrMethodNotAllowed.Error())

	NotFoundHandler = func(c Context) error {
		return c.Bytes(http.StatusNotFound, NotFoundMessage)
	}
	MethodNotAllowedHandler = func(c Context) error {
		return c.Bytes(http.StatusMethodNotAllowed, MethodNotAllowedMessage)
	}
	ErrorHandler = func(err error, c Context) {
		if err == nil {
			return
		}
		e, ok := err.(*Error)
		if !ok {
			e = ErrInternalServerError
		}
		if resp := c.Response(); !resp.Written() {
			c.String(e.Code, e.Error())
		}
	}
)

Functions

func HostMatcher

func HostMatcher(host, dst string) bool

func RegisterRule

func RegisterRule(rule string, matcher func(string) Matcher)

Types

type Context

type Context interface {
	Forest() *Forest
	Logger() Logger
	Route() *Route
	Request() *http.Request
	Response() *Response

	Next() error
	NextWith(Context) error

	Get(string) interface{}
	Set(string, interface{})

	Param(string) string
	Params() map[string]string

	FormParam(string, ...string) string
	FormParams() (url.Values, error)

	QueryParam(string, ...string) string
	QueryParams() url.Values

	Cookie(string, ...*http.Cookie) (*http.Cookie, error)
	Cookies() []*http.Cookie
	SetCookie(*http.Cookie)

	Bind(interface{}) error
	BindWith(interface{}, binder.Binder) error
	BindParams(interface{}) error
	BindHeader(interface{}) error

	XML(int, interface{}) error
	JSON(int, interface{}) error
	JSONP(int, string, interface{}) error
	HTML(int, string) error
	Bytes(int, []byte) error
	String(int, string, ...interface{}) error
	Blob(int, string, []byte) error
	Render(int, string, interface{}) error
	RenderWith(int, render.Renderer) error

	File(string) error
	FileFromFS(string, http.FileSystem) error

	URL(string, ...interface{}) string
	Status(int) error
	Redirect(int, string, ...interface{}) error
}

func NewContext

func NewContext(r *http.Request, w http.ResponseWriter) Context

type Error

type Error struct {
	Code    int         `json:"-"`
	Message interface{} `json:"message"`
}

func NewError

func NewError(code int, message ...interface{}) *Error

func (*Error) Error

func (e *Error) Error() string

type ErrorHandlerFunc

type ErrorHandlerFunc func(error, Context)

type Forest

type Forest struct {
	Server *http.Server
	// contains filtered or unexported fields
}

func New

func New(opts ...Option) *Forest

func (*Forest) MethodNotAllowed

func (e *Forest) MethodNotAllowed(handlers ...HandlerFunc) *Route

func (*Forest) Mount

func (e *Forest) Mount(child *Forest, opts ...GroupOption)

func (*Forest) MountGroup

func (e *Forest) MountGroup(child *Group, opts ...GroupOption)

func (*Forest) NewContext

func (e *Forest) NewContext(w http.ResponseWriter, r *http.Request) *context

func (*Forest) NotFound

func (e *Forest) NotFound(handlers ...HandlerFunc) *Route

func (*Forest) Route

func (e *Forest) Route(name string) *Route

func (*Forest) Routes

func (e *Forest) Routes() []*Route

func (*Forest) ServeHTTP

func (e *Forest) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Forest) SetOptions

func (e *Forest) SetOptions(opts ...Option)

func (*Forest) Shutdown

func (e *Forest) Shutdown(ctx stdcontext.Context) error

func (*Forest) Start

func (e *Forest) Start(addr string) error

func (*Forest) StartTLS

func (e *Forest) StartTLS(addr string, certFile, keyFile string) error

func (*Forest) URL

func (e *Forest) URL(name string, args ...interface{}) string

func (*Forest) Use

func (e *Forest) Use(middlewares ...HandlerFunc) *Forest

type Group

type Group struct {
	Logger       Logger
	Renderer     render.TemplateRenderer
	ErrorHandler ErrorHandlerFunc
	// contains filtered or unexported fields
}

func NewGroup

func NewGroup(opts ...GroupOption) *Group

func (*Group) Add

func (g *Group) Add(method string, path string, handlers ...HandlerFunc) *Route

func (*Group) Any

func (g *Group) Any(path string, handlers ...HandlerFunc) Routes

func (*Group) CONNECT

func (g *Group) CONNECT(path string, handlers ...HandlerFunc) *Route

func (*Group) DELETE

func (g *Group) DELETE(path string, handlers ...HandlerFunc) *Route

func (*Group) GET

func (g *Group) GET(path string, handlers ...HandlerFunc) *Route

func (*Group) Group

func (g *Group) Group(opts ...GroupOption) *Group

func (*Group) HEAD

func (g *Group) HEAD(path string, handlers ...HandlerFunc) *Route

func (*Group) Mount

func (g *Group) Mount(child *Group, opts ...GroupOption)

func (*Group) Name

func (g *Group) Name() string

func (*Group) OPTIONS

func (g *Group) OPTIONS(path string, handlers ...HandlerFunc) *Route

func (*Group) PATCH

func (g *Group) PATCH(path string, handlers ...HandlerFunc) *Route

func (*Group) POST

func (g *Group) POST(path string, handlers ...HandlerFunc) *Route

func (*Group) PUT

func (g *Group) PUT(path string, handlers ...HandlerFunc) *Route

func (*Group) SetOptions

func (g *Group) SetOptions(opts ...GroupOption)

func (*Group) Static

func (g *Group) Static(path, root string, middlewares ...HandlerFunc) *Route

func (*Group) StaticFS

func (g *Group) StaticFS(path string, fs http.FileSystem, middlewares ...HandlerFunc) *Route

func (*Group) StaticFile

func (g *Group) StaticFile(path, file string, middlewares ...HandlerFunc) *Route

func (*Group) TRACE

func (g *Group) TRACE(path string, handlers ...HandlerFunc) *Route

func (*Group) Use

func (g *Group) Use(middlewares ...HandlerFunc) *Group

type GroupOption

type GroupOption func(*Group)

func WithHost

func WithHost(host string) GroupOption

func WithMiddlewares

func WithMiddlewares(handlers ...HandlerFunc) GroupOption

func WithName

func WithName(name string) GroupOption

func WithPrefix

func WithPrefix(prefix string) GroupOption

func (GroupOption) Forest

func (opt GroupOption) Forest() Option

type H

type H map[string]interface{}

type HandlerFunc

type HandlerFunc func(Context) error

func WrapHandler

func WrapHandler(h http.Handler) HandlerFunc

func WrapHandlerFunc

func WrapHandlerFunc(h http.HandlerFunc) HandlerFunc

type Logger

type Logger interface {
	Info(...interface{})
	Warn(...interface{})
	Print(...interface{})
	Error(...interface{})
	Fatal(...interface{})
	Panic(...interface{})
	Infoln(...interface{})
	Warnln(...interface{})
	Println(...interface{})
	Errorln(...interface{})
	Fatalln(...interface{})
	Panicln(...interface{})
	Infof(string, ...interface{})
	Warnf(string, ...interface{})
	Printf(string, ...interface{})
	Errorf(string, ...interface{})
	Fatalf(string, ...interface{})
	Panicf(string, ...interface{})
}

type Matcher

type Matcher interface {
	Name() string
	// path, index, next, index is last matched index
	Match(string, int, bool) (int, bool)
}

type Option

type Option func(*Forest)

func Debug

func Debug() Option

func HostMatch

func HostMatch(matcher func(string, string) bool) Option

func Middlewares

func Middlewares(handlers ...HandlerFunc) Option

func (Option) Group

func (opt Option) Group() GroupOption

type Response

type Response struct {
	http.ResponseWriter
	Size   int
	Status int
}

func NewResponse

func NewResponse(w http.ResponseWriter) *Response

func (*Response) Write

func (r *Response) Write(b []byte) (n int, err error)

func (*Response) WriteHeader

func (r *Response) WriteHeader(code int)

func (*Response) Written

func (r *Response) Written() bool

type Route

type Route struct {
	Name string
	// contains filtered or unexported fields
}

func (*Route) Desc

func (r *Route) Desc() string

func (*Route) ErrorHandle

func (r *Route) ErrorHandle(err error, c Context)

func (*Route) Forest

func (r *Route) Forest() *Forest

func (*Route) Group

func (r *Route) Group() *Group

func (*Route) Handlers

func (r *Route) Handlers() []HandlerFunc

func (*Route) Host

func (r *Route) Host() string

func (*Route) Logger

func (r *Route) Logger() Logger

func (*Route) Method

func (r *Route) Method() string

func (*Route) Named

func (r *Route) Named(name string, desc ...string) *Route

func (*Route) Path

func (r *Route) Path() string

func (*Route) Render

func (r *Route) Render(w http.ResponseWriter, name string, data interface{}) error

func (*Route) String

func (r *Route) String() string

func (*Route) URL

func (r *Route) URL(args ...interface{}) string

type Routes

type Routes []*Route

Directories

Path Synopsis
contrib module
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL