diff --git a/README.md b/README.md index 3e55e68..afd6693 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ import ( ) func notImplementedHandler(w http.ResponseWriter, r *http.Request, p goat.Params) { - goat.WriteError(w, "Not implemented") + goat.WriteError(w, 500, "Not implemented") } func helloHandler(w http.ResponseWriter, r *http.Request, p goat.Params) { @@ -37,6 +37,23 @@ func main() { } ``` +## Features +### Groups +You can group the routes by a prefix. This can have a serious impact on the +readability of your code. + +### Indices +Every route can have a description (like `user_login_url`). These can be used +to automagically generate an API index (like [this](https://api.github.com)). +If you want to hide specific methods, just provide an empty string. + +**Note:** Indices are only supported for `GET` requests. Open an issue, if you +want them on other methods, too + +### Helpers +You can quickly pretty print JSON to a `http.ResponseWriter` using +`goat.WriteJSON` or `goat.WriteError`. + ## Roadmap * [x] Subrouters or Grouping * [ ] Middleware diff --git a/goat.go b/goat.go index 15da547..20c32c3 100644 --- a/goat.go +++ b/goat.go @@ -1,6 +1,10 @@ package goat -import "github.com/julienschmidt/httprouter" +import ( + "net/http" + + "github.com/julienschmidt/httprouter" +) // New creates a new Router and returns it func New() *Router { @@ -12,3 +16,8 @@ func New() *Router { return r } + +// Run starts the server +func (r *Router) Run(address string) error { + return http.ListenAndServe(address, r.chain()) +} diff --git a/index.go b/index.go new file mode 100644 index 0000000..0cd6279 --- /dev/null +++ b/index.go @@ -0,0 +1,38 @@ +package goat + +import ( + "net/http" + "sort" +) + +// IndexHandler writes the index of all GET methods to the ResponseWriter +func (r *Router) IndexHandler(w http.ResponseWriter, _ *http.Request, _ Params) { + WriteJSON(w, r.Index()) +} + +// Index returns a string map with the titles and urls of all GET routes +func (r *Router) Index() map[string]string { + index := r.index + + // Recursion + for _, sr := range r.children { + si := sr.Index() + + for k, v := range si { + index[k] = v + } + } + + // Sort + sorted := make(map[string]string) + var keys []string + for k := range index { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + sorted[k] = index[k] + } + + return sorted +} diff --git a/index_test.go b/index_test.go new file mode 100644 index 0000000..e616e64 --- /dev/null +++ b/index_test.go @@ -0,0 +1,23 @@ +package goat + +import ( + "reflect" + "testing" +) + +func TestIndex(t *testing.T) { + r := New() + + r.Get("/foo/bar", "foo_bar_url", emptyHandler) + r.Get("/bar", "bar_url", emptyHandler) + r.Post("/foo", "foo_url", emptyHandler) + + out := r.Index() + expected := map[string]string{ + "bar_url": "/bar", + "foo_bar_url": "/foo/bar", + } + if !reflect.DeepEqual(out, expected) { + t.Errorf("Index should regurn %v, but did return %v", expected, out) + } +} diff --git a/middleware.go b/middleware.go new file mode 100644 index 0000000..0b10949 --- /dev/null +++ b/middleware.go @@ -0,0 +1,26 @@ +package goat + +import "net/http" + +// Middleware reprents a default middleware function +type Middleware func(http.Handler) http.Handler + +// chain calls all middlewares and returns the final handler +func (r *Router) chain() http.Handler { + var final http.Handler + final = r.router + + for i := len(r.middleware) - 1; i >= 0; i-- { + final = r.middleware[i](final) + } + + return final +} + +// Use adds middleware to the router +func (r *Router) Use(middleware ...Middleware) { + if r.parent != nil { + panic("subrouters can't use middleware!") + } + r.middleware = append(r.middleware, middleware...) +} diff --git a/middleware_test.go b/middleware_test.go new file mode 100644 index 0000000..0008f1f --- /dev/null +++ b/middleware_test.go @@ -0,0 +1,16 @@ +package goat + +import ( + "net/http" + "testing" +) + +func TestUse(t *testing.T) { + r := New() + mw := func(h http.Handler) http.Handler { return nil } + //exp := []Middleware{mw} + + r.Use(mw) + + // TODO: Test function equality +} diff --git a/router.go b/router.go index e060c43..c0c3381 100644 --- a/router.go +++ b/router.go @@ -2,7 +2,6 @@ package goat import ( "net/http" - "sort" "github.com/julienschmidt/httprouter" ) @@ -21,31 +20,27 @@ type Router struct { // The router router *httprouter.Router + + // Middleware + middleware []Middleware } // Handle describes the function that should be used by handlers type Handle func(http.ResponseWriter, *http.Request, Params) -// notFoundHandler handles (as you already know) the 404 error -func (r *Router) notFoundHandler(w http.ResponseWriter, req *http.Request) { - WriteError(w, 404, "404 Not Found") -} - -// ServeHTTP calls the same method on the router -func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - r.router.ServeHTTP(w, req) -} - -// subPath returns the prefix of the router + the given path and eliminates -// duplicate slashes -func (r *Router) subPath(p string) string { - pre := r.prefix - - if (pre == "/" || pre[:len(pre)-1] == "/") && p[:1] == "/" { - pre = pre[:len(pre)-1] +// Subrouter creates and returns a subrouter +func (r *Router) Subrouter(path string) *Router { + sr := &Router{ + index: make(map[string]string), + prefix: r.subPath(path), + router: r.router, } - return pre + p + // Init relationships + r.children = append(r.children, sr) + sr.parent = r + + return sr } // addRoute adds a route to the index and passes it over to the httprouter @@ -87,49 +82,19 @@ func (r *Router) Put(path, title string, fn Handle) { r.addRoute("PUT", path, title, fn) } -// Subrouter creates and returns a subrouter -func (r *Router) Subrouter(path string) *Router { - sr := &Router{ - index: make(map[string]string), - prefix: r.subPath(path), - router: r.router, - } - - // Init relationships - r.children = append(r.children, sr) - sr.parent = r - - return sr -} - -// IndexHandler writes the index of all GET methods to the ResponseWriter -func (r *Router) IndexHandler(w http.ResponseWriter, _ *http.Request, _ Params) { - WriteJSON(w, r.Index()) +// notFoundHandler handles (as you already know) the 404 error +func (r *Router) notFoundHandler(w http.ResponseWriter, req *http.Request) { + WriteError(w, 404, "404 Not Found") } -// Index returns a string map with the titles and urls of all GET routes -func (r *Router) Index() map[string]string { - index := r.index - - // Recursion - for _, sr := range r.children { - si := sr.Index() - - for k, v := range si { - index[k] = v - } - } +// subPath returns the prefix of the router + the given path and eliminates +// duplicate slashes +func (r *Router) subPath(p string) string { + pre := r.prefix - // Sort - sorted := make(map[string]string) - var keys []string - for k := range index { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - sorted[k] = index[k] + if (pre == "/" || pre[:len(pre)-1] == "/") && p[:1] == "/" { + pre = pre[:len(pre)-1] } - return sorted + return pre + p } diff --git a/router_test.go b/router_test.go index 7493938..df884e9 100644 --- a/router_test.go +++ b/router_test.go @@ -36,23 +36,6 @@ func TestAddRoute(t *testing.T) { } } -func TestIndex(t *testing.T) { - r := New() - - r.Get("/foo/bar", "foo_bar_url", emptyHandler) - r.Get("/bar", "bar_url", emptyHandler) - r.Post("/foo", "foo_url", emptyHandler) - - out := r.Index() - expected := map[string]string{ - "bar_url": "/bar", - "foo_bar_url": "/foo/bar", - } - if !reflect.DeepEqual(out, expected) { - t.Errorf("Index should regurn %v, but did return %v", expected, out) - } -} - func TestSubrouter(t *testing.T) { pre := "/user" r := New()