box

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: May 20, 2023 License: MIT Imports: 15 Imported by: 5

README

Box

Build Status GoDoc GitHub release (latest SemVer)

Box is an HTTP router to speed up development. Box supports URL parameters, interceptors, magic handlers and introspection documentation.

Getting started

package main

import (
	"github.com/fulldump/box"
)

func main() {

    b := box.NewBox()

    b.HandleFunc("GET", "/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("World!"))
    })

    b.ListenAndServe() // listening at http://localhost:8080

}

Sending JSON

b := box.NewBox()

type MyResponse struct {
    Name string
    Age  int
}

b.Handle("GET", "/hello", func(w http.ResponseWriter, r *http.Request) MyResponse {
    return MyResponse{
        Name: "Fulanez",
        Age:  33,
    }
})

URL parameters

b := box.NewBox()

b.Handle("GET", "/articles/{article-id}", func(w http.ResponseWriter, r *http.Request) string {
    articleID := box.Param(r, "article-id")
    return "ArticleID is " + articleID
})

Receiving and sending JSON

type CreateArticleRequest struct {
    Title string
    Text  string
}

type Article struct {
    Id      string    `json:"id"`
    Title   string    `json:"title"`
    Text    string    `json:"text"`
    Created time.Time `json:"created"`
}

b := box.NewBox()
b.Handle("POST", "/articles", func(input CreateArticleRequest) Article {
    fmt.Println("Persist new article...", input)
    return Article{
        Id:      "my-new-id",
        Title:   input.Title,
        Text:    input.Text,
        Created: time.Unix(1674762079, 0),
    }
})

Use interceptors

Interceptors, also known as middlewares, are pieces of code that are executed in order before the handler to provide common functionality:

  • Do things before and/or after the handler execution
  • Cut the execution and stop executing the rest of interceptors and handler
  • Inject items into the context
func ListArticles()   { /* ... */ }
func CreateArticles() { /* ... */ }
func GetArticle()     { /* ... */ }
func DeleteArticle()  { /* ... */ }

func main() {
    b := box.NewBox()

    b.Use(box.AccessLog)   // use middlewares to print logs
    b.Use(box.PrettyError) // use middlewares return pretty errors

    b.Handle("GET", "/articles", ListArticles)
    b.Handle("POST", "/articles", CreateArticles)
    b.Handle("GET", "/articles/{article-id}", GetArticle)
    b.Handle("DELETE", "/articles/{article-id}", DeleteArticle)
}

Error handling

b := box.NewBox()
b.Use(box.PrettyError)
b.Handle("GET", "/articles", func() (*Article, error) {
    return nil, errors.New("could not connect to the database")
})
go b.ListenAndServe()

resp, _ := http.Get(s.URL + "/articles")
io.Copy(os.Stdout, resp.Body) // could not connect to the database

Groups

Groups are a neat way to organize and compose big APIs and also to limit the scope of interceptors.

b := box.NewBox()

v0 := b.Group("/v0")
v0.Use(box.SetResponseHeader("Content-Type", "application/json"))

v0.Handle("GET", "/articles", ListArticles)
v0.Handle("POST", "/articles", CreateArticle)

Custom interceptors

Interceptors are very useful to reuse logic in a very convenient and modular way.

Here is a sample interceptor that does nothing:

func MyCustomInterceptor(next box.H) box.H {
	return func(ctx context.Context) {
        // do something before the handler
		next(ctx) // continue the flow
		// do something after the handler
	}
}

The following interceptor returns a Server header:

func MyCustomInterceptor(next box.H) box.H {
	return func(ctx context.Context) {
		w := box.GetResponse(ctx)
		w.Header().Set("Server", "MyServer")
		next(ctx) // continue the flow
	}
}

func main() {

	b := box.NewBox()
	b.Use(MyCustomInterceptor)

}

Parametrized interceptors

Sometimes interceptors can be generalized to cover a wider set of use cases. For example, the following interceptor can set any response header and can be used multiple times.

func SetResponseHeader(key, value string) box.I {
	return func(next box.H) box.H {
		return func(ctx context.Context) {
			box.GetResponse(ctx).Header().Set(key, value)
			next(ctx)
		}
	}
}

func main() {
    b := box.NewBox()
    b.Use(
        box.SetResponseHeader("Server", "My server name"),
        box.SetResponseHeader("Version", "v3.2.1"),
    )
}

Documentation

Index

Constants

View Source
const (
	AttrHttpMethod = "attr_http_method"
	AttrHttpBind   = "attr_http_bind"
	AttrDoc        = "attr_doc"
)
View Source
const HttpMethodAny = "*"

Variables

View Source
var (
	ErrResourceNotFound = errors.New("resource_not_found")
	ErrMethodNotAllowed = errors.New("method_not_allowed")
)
View Source
var DefaultAccessLogPrintln = log.Println

Functions

func Box2Http

func Box2Http(b *B) http.Handler

func GetError

func GetError(ctx context.Context) error

func GetRequest

func GetRequest(ctx context.Context) *http.Request

func GetResponse

func GetResponse(ctx context.Context) http.ResponseWriter

func GetUrlParameter added in v0.2.0

func GetUrlParameter(ctx context.Context, param string) string

func Param added in v0.3.0

func Param(r *http.Request, param string) string

func SetBoxContext added in v0.2.0

func SetBoxContext(ctx context.Context, c *C) context.Context

func SetError

func SetError(ctx context.Context, err error)

Types

type A

type A struct {
	Attr
	HttpMethod string

	// Name is the name to identify the action AND the invocation to url suffix
	Name string

	// Bound is true if this action is not an extended action
	Bound bool

	// Interceptors is the list of actions that will be executed before executing handler
	Interceptors []I
	// contains filtered or unexported fields
}

An A stands for Action

func Action

func Action(handler interface{}) *A

func ActionPost

func ActionPost(handler interface{}) *A

func AnyMethod added in v0.2.0

func AnyMethod(handler interface{}) *A

func Connect

func Connect(handler interface{}) *A

func Delete

func Delete(handler interface{}) *A

func Get

func Get(handler interface{}) *A

Bind shortcuts:

func Head(handler interface{}) *A

func Options

func Options(handler interface{}) *A

func Patch

func Patch(handler interface{}) *A

func Post

func Post(handler interface{}) *A

func Put

func Put(handler interface{}) *A

func Trace

func Trace(handler interface{}) *A

func (*A) Bind

func (a *A) Bind(method string) *A

func (*A) GetHandler added in v0.3.1

func (a *A) GetHandler() any

GetHandler overwrite default action name

func (*A) Use added in v0.3.0

func (a *A) Use(interceptor ...I) *A

Use is an alias of WithInterceptors

func (*A) WithAttribute

func (a *A) WithAttribute(key string, value interface{}) *A

func (*A) WithInterceptors

func (a *A) WithInterceptors(interceptor ...I) *A

func (*A) WithName

func (a *A) WithName(name string) *A

WithName overwrite default action name

type Attr

type Attr map[string]interface{}

func (Attr) GetAttribute

func (a Attr) GetAttribute(key string) (value interface{})

Get Attribute value using key string from Box, Resource or Action.

func (Attr) SetAttribute

func (a Attr) SetAttribute(key string, value interface{})

Set Attribute key-value to Box, Resource or Action.

type B

type B struct {
	// R is the root resource in box
	*R
	HttpHandler http.Handler
}

func NewBox

func NewBox() *B

func (*B) ListenAndServe added in v0.1.4

func (b *B) ListenAndServe() error

ListenAndServe is a helper that creates a new http.Server and call to its method ListenAndServe on address :8080

func (*B) Serve

func (b *B) Serve()

Serve is a helper that creates a new http.Server and call to its method ListenAndServe on address :8080 Deprecated: Use the analogous name b.ListenAndServe

func (*B) ServeHTTP added in v0.1.4

func (b *B) ServeHTTP(w http.ResponseWriter, r *http.Request)

type C

type C struct {
	Resource   *R
	Action     *A
	Parameters map[string]string
	// TODO: add headers
	// TODO: add query
	// TODO: add box
	// TODO: ¿add marshaler and unmarshaler?
	Request  *http.Request
	Response http.ResponseWriter
	// contains filtered or unexported fields
}

An C is a box context to store box related thing in context such as *R, *A, *E, etc

func GetBoxContext added in v0.2.0

func GetBoxContext(ctx context.Context) *C

type E

type E interface {
	error
}

An E is a box error :D

type H

type H func(ctx context.Context)

An H stands for Handler

func AccessLog added in v0.3.0

func AccessLog(next H) H

func PrettyError added in v0.3.0

func PrettyError(next H) H

func RecoverFromPanic added in v0.3.0

func RecoverFromPanic(next H) H

RecoverFromPanic is an interceptor to recover and pretty print a stacktrace

type I

type I func(next H) H

An I stands for Interceptor

func SetResponseHeader added in v0.3.0

func SetResponseHeader(key, value string) I

type R

type R struct {
	Attr

	// Path is a literal or placeholder that matches with a portion of the path
	Path string

	// Parent is a reference to parent resource
	Parent *R

	// Children is the list of descendent resources
	Children []*R

	// Interceptors is the list of actions that will be executed before each
	// action or resource under this resource
	Interceptors []I
	// contains filtered or unexported fields
}

R stands for Resource

func NewResource

func NewResource() *R

func (*R) GetActions added in v0.1.0

func (r *R) GetActions() []*A

GetActions retrieve the slice of actions defined in this resource

func (*R) Group added in v0.3.0

func (r *R) Group(path string) *R

Group is an alias of Resource

func (*R) Handle added in v0.2.1

func (r *R) Handle(method string, path string, handler interface{}) *A

func (*R) HandleFunc added in v0.2.1

func (r *R) HandleFunc(method string, path string, handler http.HandlerFunc) *A

func (*R) Match

func (r *R) Match(path string, parameters map[string]string) (result *R)

TODO: maybe parameters should be a type `P`

func (*R) Resource

func (r *R) Resource(locator string) *R

Resource defines a new resource below current resource

func (*R) Use added in v0.3.0

func (r *R) Use(interceptor ...I) *R

Use is an alias of WithInterceptors

func (*R) WithActions

func (r *R) WithActions(action ...*A) *R

Add action to this resource

func (*R) WithAttribute

func (r *R) WithAttribute(key string, value interface{}) *R

func (*R) WithInterceptors

func (r *R) WithInterceptors(interceptor ...I) *R

Add interceptor to this resource

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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