From 0a5a067aea1846d43329de8d862a97f041981314 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Fri, 1 Aug 2014 16:25:40 +0200 Subject: [PATCH 1/5] Update README with features --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 From d1a242d0a9343f4a33f640c128f15d1e61491971 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Fri, 1 Aug 2014 17:05:57 +0200 Subject: [PATCH 2/5] Remove json files --- goat.go | 27 ++++++++++++++++++++++++++- json.go | 26 -------------------------- json_test.go | 1 - 3 files changed, 26 insertions(+), 28 deletions(-) delete mode 100644 json.go delete mode 100644 json_test.go diff --git a/goat.go b/goat.go index 15da547..a44a37b 100644 --- a/goat.go +++ b/goat.go @@ -1,6 +1,11 @@ package goat -import "github.com/julienschmidt/httprouter" +import ( + "encoding/json" + "net/http" + + "github.com/julienschmidt/httprouter" +) // New creates a new Router and returns it func New() *Router { @@ -12,3 +17,23 @@ func New() *Router { return r } + +// WriteError writes a string as JSON encoded error +func WriteError(w http.ResponseWriter, code int, err string) { + w.WriteHeader(code) + + WriteJSON(w, map[string]string{ + "error": err, + }) +} + +// WriteJSON writes the given interface as JSON or returns an error +func WriteJSON(w http.ResponseWriter, v interface{}) error { + b, err := json.MarshalIndent(v, "", " ") + if err != nil { + return err + } + + w.Write(b) + return nil +} diff --git a/json.go b/json.go deleted file mode 100644 index 9bdd56d..0000000 --- a/json.go +++ /dev/null @@ -1,26 +0,0 @@ -package goat - -import ( - "encoding/json" - "net/http" -) - -// WriteError writes a string as JSON encoded error -func WriteError(w http.ResponseWriter, code int, err string) { - w.WriteHeader(code) - - WriteJSON(w, map[string]string{ - "error": err, - }) -} - -// WriteJSON writes the given interface as JSON or returns an error -func WriteJSON(w http.ResponseWriter, v interface{}) error { - b, err := json.MarshalIndent(v, "", " ") - if err != nil { - return err - } - - w.Write(b) - return nil -} diff --git a/json_test.go b/json_test.go deleted file mode 100644 index b7f8451..0000000 --- a/json_test.go +++ /dev/null @@ -1 +0,0 @@ -package goat From a0dde5b7afa5e9143dbb8a1839244aadde6f0349 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Fri, 1 Aug 2014 17:39:29 +0200 Subject: [PATCH 3/5] Add middleware --- router.go | 14 ++++++++++++++ router_test.go | 11 +++++++++++ 2 files changed, 25 insertions(+) diff --git a/router.go b/router.go index e060c43..06da58f 100644 --- a/router.go +++ b/router.go @@ -21,11 +21,17 @@ 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) +// Middleware reprents a default middleware function +type Middleware func(http.Handler) http.Handler + // 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") @@ -102,6 +108,14 @@ func (r *Router) Subrouter(path string) *Router { return sr } +// 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...) +} + // 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()) diff --git a/router_test.go b/router_test.go index 7493938..4d02c83 100644 --- a/router_test.go +++ b/router_test.go @@ -1,6 +1,7 @@ package goat import ( + "net/http" "reflect" "testing" ) @@ -70,3 +71,13 @@ func TestSubrouter(t *testing.T) { t.Errorf("Subrouter should add %v to children of %v, but didn't", sr, r) } } + +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 +} From 766e9917e4787fa54d69aac271a7786f5ce3dde0 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Fri, 1 Aug 2014 17:47:55 +0200 Subject: [PATCH 4/5] Add Router.chain() for middleware chaining --- router.go | 17 +++++++++++++++++ router_test.go | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/router.go b/router.go index 06da58f..013d62e 100644 --- a/router.go +++ b/router.go @@ -116,6 +116,23 @@ func (r *Router) Use(middleware ...Middleware) { r.middleware = append(r.middleware, middleware...) } +// Run starts the server +func (r *Router) Run(address string) error { + return http.ListenAndServe(address, r.chain()) +} + +// 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 +} + // 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()) diff --git a/router_test.go b/router_test.go index 4d02c83..6e121c7 100644 --- a/router_test.go +++ b/router_test.go @@ -75,7 +75,7 @@ func TestSubrouter(t *testing.T) { func TestUse(t *testing.T) { r := New() mw := func(h http.Handler) http.Handler { return nil } - exp := []Middleware{mw} + //exp := []Middleware{mw} r.Use(mw) From 0c2fefe360e5177602f5a822f99effafd4be8d81 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Fri, 1 Aug 2014 17:51:57 +0200 Subject: [PATCH 5/5] Split everything into files by responsibility --- goat.go | 22 ++------- index.go | 38 ++++++++++++++++ index_test.go | 23 ++++++++++ json.go | 26 +++++++++++ json_test.go | 1 + middleware.go | 26 +++++++++++ middleware_test.go | 16 +++++++ router.go | 108 +++++++++------------------------------------ router_test.go | 28 ------------ 9 files changed, 154 insertions(+), 134 deletions(-) create mode 100644 index.go create mode 100644 index_test.go create mode 100644 json.go create mode 100644 json_test.go create mode 100644 middleware.go create mode 100644 middleware_test.go diff --git a/goat.go b/goat.go index a44a37b..20c32c3 100644 --- a/goat.go +++ b/goat.go @@ -1,7 +1,6 @@ package goat import ( - "encoding/json" "net/http" "github.com/julienschmidt/httprouter" @@ -18,22 +17,7 @@ func New() *Router { return r } -// WriteError writes a string as JSON encoded error -func WriteError(w http.ResponseWriter, code int, err string) { - w.WriteHeader(code) - - WriteJSON(w, map[string]string{ - "error": err, - }) -} - -// WriteJSON writes the given interface as JSON or returns an error -func WriteJSON(w http.ResponseWriter, v interface{}) error { - b, err := json.MarshalIndent(v, "", " ") - if err != nil { - return err - } - - w.Write(b) - return nil +// 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/json.go b/json.go new file mode 100644 index 0000000..9bdd56d --- /dev/null +++ b/json.go @@ -0,0 +1,26 @@ +package goat + +import ( + "encoding/json" + "net/http" +) + +// WriteError writes a string as JSON encoded error +func WriteError(w http.ResponseWriter, code int, err string) { + w.WriteHeader(code) + + WriteJSON(w, map[string]string{ + "error": err, + }) +} + +// WriteJSON writes the given interface as JSON or returns an error +func WriteJSON(w http.ResponseWriter, v interface{}) error { + b, err := json.MarshalIndent(v, "", " ") + if err != nil { + return err + } + + w.Write(b) + return nil +} diff --git a/json_test.go b/json_test.go new file mode 100644 index 0000000..b7f8451 --- /dev/null +++ b/json_test.go @@ -0,0 +1 @@ +package goat 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 013d62e..c0c3381 100644 --- a/router.go +++ b/router.go @@ -2,7 +2,6 @@ package goat import ( "net/http" - "sort" "github.com/julienschmidt/httprouter" ) @@ -29,29 +28,19 @@ type Router struct { // Handle describes the function that should be used by handlers type Handle func(http.ResponseWriter, *http.Request, Params) -// Middleware reprents a default middleware function -type Middleware func(http.Handler) http.Handler - -// 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 @@ -93,74 +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 -} - -// 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...) -} - -// Run starts the server -func (r *Router) Run(address string) error { - return http.ListenAndServe(address, r.chain()) -} - -// 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 -} - -// 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 6e121c7..df884e9 100644 --- a/router_test.go +++ b/router_test.go @@ -1,7 +1,6 @@ package goat import ( - "net/http" "reflect" "testing" ) @@ -37,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() @@ -71,13 +53,3 @@ func TestSubrouter(t *testing.T) { t.Errorf("Subrouter should add %v to children of %v, but didn't", sr, r) } } - -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 -}