diff options
| author | Shulhan <m.shulhan@gmail.com> | 2019-09-12 00:59:53 +0700 |
|---|---|---|
| committer | Shulhan <m.shulhan@gmail.com> | 2019-09-12 00:59:53 +0700 |
| commit | b938bb94426f71105d572df56b22528b7a76d96f (patch) | |
| tree | 3bfcbd463450e7a94bcaccce9eab12314c6ca9f8 | |
| parent | d8368c713d49ef0606a2c59baec0e55f0c61baea (diff) | |
| download | pakakeh.go-b938bb94426f71105d572df56b22528b7a76d96f.tar.xz | |
http: implement key binding in registered Endpoint's Path
Previously, only raw path can be registered on Endpoint. This changes
implement key binding using colon ":" on path. For example, registering
path "/:x/y" will bind key "x" to a string value that can be accessed on
http.Request.Form using Get method.
| -rw-r--r-- | CHANGELOG.adoc | 22 | ||||
| -rw-r--r-- | lib/http/endpoint.go | 15 | ||||
| -rw-r--r-- | lib/http/http.go | 66 | ||||
| -rw-r--r-- | lib/http/server.go | 334 | ||||
| -rw-r--r-- | lib/http/server_test.go | 220 |
5 files changed, 490 insertions, 167 deletions
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index d49abc7c..652de861 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -5,6 +5,28 @@ This library is released each month, usually at the first week of month. +== share v0.9.0 (2019-10-xx) + +=== New Features + +* http: implement key binding in registered Endpoint's Path ++ +Previously, only raw path can be registered on Endpoint. This changes +implement key binding using colon ":" on path. For example, registering +path "/:x/y" will bind key "x" to a string value that can be accessed on +http.Request.Form using Get method. + + +=== Bug Fixes + +* ini: check for possible nil variable on Write + +* dns: allow message with non recursive-desired to be forwarded ++ +On macOS, turn out, all DNS queries have RD flag set to zero. This cause +no DNS queries forwarded to parent server. + + == share v0.8.2 (2019-09-05) === Enhancements diff --git a/lib/http/endpoint.go b/lib/http/endpoint.go index db80990c..c56f5e76 100644 --- a/lib/http/endpoint.go +++ b/lib/http/endpoint.go @@ -24,21 +24,29 @@ import ( type Endpoint struct { // Method contains HTTP method, default to GET. Method RequestMethod + // Path contains route to be served, default to "/" if its empty. Path string + // RequestType contains type of request, default to RequestTypeNone. RequestType RequestType + // ResponseType contains type of request, default to ResponseTypeNone. ResponseType ResponseType + // Eval define evaluator for route that will be called after global // evaluators and before callback. Eval Evaluator + // Call is the main process of route. Call Callback } -func (ep *Endpoint) call(res http.ResponseWriter, req *http.Request, +func (ep *Endpoint) call( + res http.ResponseWriter, + req *http.Request, evaluators []Evaluator, + vals map[string]string, ) { reqBody, e := ioutil.ReadAll(req.Body) if e != nil { @@ -68,6 +76,11 @@ func (ep *Endpoint) call(res http.ResponseWriter, req *http.Request, if debug.Value > 0 { log.Printf("> request body: %s\n", reqBody) } + for k, v := range vals { + if len(k) > 0 && len(v) > 0 { + req.Form.Set(k, v) + } + } for _, eval := range evaluators { e = eval(req, reqBody) diff --git a/lib/http/http.go b/lib/http/http.go index 5d9141cc..a87bed02 100644 --- a/lib/http/http.go +++ b/lib/http/http.go @@ -69,7 +69,7 @@ // ResponseType: libhttp.ResponseTypeJSON, // Call: handleLogin, // } -// server.RequestPost(epAPILogin) +// server.RegisterPost(epAPILogin) // // Upon receiving request to "/api/login", the library will call // "req.ParseForm()", read the content of body and pass them to @@ -81,14 +81,55 @@ // // Return response body and error. // } // -// Known Bugs +// Routing +// +// The Endpoint allow binding the unique key into path using colon ":" as the +// first character. +// +// For example, after registering the following Endpoint, +// +// epBinding := &libhttp.Endpoint{ +// Path: "/category/:name", +// RequestType: libhttp.RequestTypeQuery, +// ResponseType: libhttp.ResponseTypeJSON, +// Call: handleCategory, +// } +// server.RegisterGet(epBinding) +// +// when the server receiving GET request using path "/category/book?limit=10", +// it will put the "book" and "10" into http.Request's Form with key is "name" +// and "limit" +// +// fmt.Printf("request.Form:", req.Form) +// // request.Form: map[name:[book] limit:[10]] +// +// The key binding must be unique between path and query. If query has the +// same key then it will be overridden by value in path. For example, using +// the above endpoint, request with "/category/book?name=Hitchiker" will +// result in Request.Form: +// +// map[name:[book]] +// +// not +// +// map[name:[book Hitchiker]] +// +// Known Bugs and Limitations // // * The server does not handle CONNECT method // // * Missing test for request with content-type multipart-form // +// * We can not register path with ambigous route. For example, "/:x" and +// "/y" are ambiguous because one is dynamic path using key binding "x" and +// the last one is static path to "y". +// package http +import ( + "errors" +) + const ( ContentEncoding = "Content-Encoding" ContentLength = "Content-Length" @@ -99,3 +140,24 @@ const ( ContentTypePlain = "text/plain; charset=utf-8" HeaderLocation = "Location" ) + +var ( + // + // ErrEndpointAmbiguous define an error when registering path that + // already exist. For example, after registering "/:x", registering + // "/:y" or "/z" on the same HTTP method will result in ambiguous. + // + ErrEndpointAmbiguous = errors.New("ambigous endpoint") + + // + // ErrEndpointKeyDuplicate define an error when registering path with + // the same keys, for example "/:x/:x". + // + ErrEndpointKeyDuplicate = errors.New("duplicate key in route") + + // + // ErrEndpointKeyEmpty define an error when path contains an empty + // key, for example "/:/y". + // + ErrEndpointKeyEmpty = errors.New("empty route's key") +) diff --git a/lib/http/server.go b/lib/http/server.go index 0d8f9934..d313556b 100644 --- a/lib/http/server.go +++ b/lib/http/server.go @@ -23,14 +23,14 @@ import ( // Server define HTTP server. // type Server struct { - mfs *memfs.MemFS - evals []Evaluator - conn *http.Server - regDelete map[string]*Endpoint - regGet map[string]*Endpoint - regPatch map[string]*Endpoint - regPost map[string]*Endpoint - regPut map[string]*Endpoint + mfs *memfs.MemFS + evals []Evaluator + conn *http.Server + routeDeletes []*route + routeGets []*route + routePatches []*route + routePosts []*route + routePuts []*route } // @@ -38,13 +38,7 @@ type Server struct { // with custom connection. // func NewServer(opts *ServerOptions) (srv *Server, e error) { - srv = &Server{ - regDelete: make(map[string]*Endpoint), - regGet: make(map[string]*Endpoint), - regPatch: make(map[string]*Endpoint), - regPost: make(map[string]*Endpoint), - regPut: make(map[string]*Endpoint), - } + srv = &Server{} if len(opts.Address) == 0 { opts.Address = ":80" @@ -90,14 +84,33 @@ func (srv *Server) RedirectTemp(res http.ResponseWriter, redirectURL string) { } // -// RegisterDelete register HTTP method DELETE with callback to handle it. +// RegisterDelete register HTTP method DELETE with specific endpoint to handle +// it. // -func (srv *Server) RegisterDelete(ep *Endpoint) { - if ep != nil { - ep.Method = RequestMethodDelete - ep.RequestType = RequestTypeQuery - srv.register(ep) +func (srv *Server) RegisterDelete(ep *Endpoint) (err error) { + if ep == nil || ep.Call == nil { + return nil } + + ep.Method = RequestMethodDelete + ep.RequestType = RequestTypeQuery + + // Check if the same route already registered. + for _, rute := range srv.routeDeletes { + _, ok := rute.parse(ep.Path) + if ok { + return ErrEndpointAmbiguous + } + } + + rute, err := newRoute(ep) + if err != nil { + return err + } + + srv.routeDeletes = append(srv.routeDeletes, rute) + + return nil } // @@ -111,93 +124,152 @@ func (srv *Server) RegisterEvaluator(eval Evaluator) { // // RegisterGet register HTTP method GET with callback to handle it. // -func (srv *Server) RegisterGet(ep *Endpoint) { - if ep != nil { - ep.Method = RequestMethodGet - ep.RequestType = RequestTypeQuery - srv.register(ep) +func (srv *Server) RegisterGet(ep *Endpoint) (err error) { + if ep == nil || ep.Call == nil { + return nil } + + ep.Method = RequestMethodGet + ep.RequestType = RequestTypeQuery + + // Check if the same route already registered. + for _, rute := range srv.routeGets { + _, ok := rute.parse(ep.Path) + if ok { + return ErrEndpointAmbiguous + } + } + + rute, err := newRoute(ep) + if err != nil { + return err + } + + srv.routeGets = append(srv.routeGets, rute) + + return nil } // // RegisterPatch register HTTP method PATCH with callback to handle it. // -func (srv *Server) RegisterPatch(ep *Endpoint) { - if ep != nil { - ep.Method = RequestMethodPatch - srv.register(ep) +func (srv *Server) RegisterPatch(ep *Endpoint) (err error) { + if ep == nil || ep.Call == nil { + return nil + } + + ep.Method = RequestMethodPatch + + // Check if the same route already registered. + for _, rute := range srv.routePatches { + _, ok := rute.parse(ep.Path) + if ok { + return ErrEndpointAmbiguous + } + } + + rute, err := newRoute(ep) + if err != nil { + return err } + + srv.routePatches = append(srv.routePatches, rute) + + return nil } // // RegisterPost register HTTP method POST with callback to handle it. // -func (srv *Server) RegisterPost(ep *Endpoint) { - if ep != nil { - ep.Method = RequestMethodPost - srv.register(ep) +func (srv *Server) RegisterPost(ep *Endpoint) (err error) { + if ep == nil || ep.Call == nil { + return nil } + + ep.Method = RequestMethodPost + + // Check if the same route already registered. + for _, rute := range srv.routePosts { + _, ok := rute.parse(ep.Path) + if ok { + return ErrEndpointAmbiguous + } + } + + rute, err := newRoute(ep) + if err != nil { + return err + } + + srv.routePosts = append(srv.routePosts, rute) + + return nil + } // // RegisterPut register HTTP method PUT with callback to handle it. // -func (srv *Server) RegisterPut(ep *Endpoint) { - if ep != nil { - ep.Method = RequestMethodPut - ep.ResponseType = ResponseTypeNone - srv.register(ep) +func (srv *Server) RegisterPut(ep *Endpoint) (err error) { + if ep == nil || ep.Call == nil { + return nil + } + + ep.Method = RequestMethodPut + ep.ResponseType = ResponseTypeNone + + // Check if the same route already registered. + for _, rute := range srv.routePuts { + _, ok := rute.parse(ep.Path) + if ok { + return ErrEndpointAmbiguous + } + } + + rute, err := newRoute(ep) + if err != nil { + return err } + + srv.routePuts = append(srv.routePuts, rute) + + return nil } // // ServeHTTP handle mapping of client request to registered endpoints. // func (srv *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { - var ( - ep *Endpoint - ok bool - ) - if debug.Value > 0 { log.Printf("> ServeHTTP: %s %+v\n", req.Method, req.URL) } switch req.Method { case http.MethodDelete: - ep, ok = srv.regDelete[req.URL.Path] + srv.handleDelete(res, req) case http.MethodGet: srv.handleGet(res, req) - return case http.MethodHead: srv.handleHead(res, req) - return case http.MethodOptions: srv.handleOptions(res, req) - return case http.MethodPatch: - ep, ok = srv.regPatch[req.URL.Path] + srv.handlePatch(res, req) case http.MethodPost: - ep, ok = srv.regPost[req.URL.Path] + srv.handlePost(res, req) case http.MethodPut: - ep, ok = srv.regPut[req.URL.Path] + srv.handlePut(res, req) default: res.WriteHeader(http.StatusNotImplemented) return } - if !ok { - res.WriteHeader(http.StatusNotFound) - return - } - - ep.call(res, req, srv.evals) } // @@ -242,6 +314,21 @@ func (srv *Server) getFSNode(reqPath string) (node *memfs.Node) { return node } +// +// handleDelete handle the DELETE request by searching the registered route +// and calling the endpoint. +// +func (srv *Server) handleDelete(res http.ResponseWriter, req *http.Request) { + for _, rute := range srv.routeDeletes { + vals, ok := rute.parse(req.URL.Path) + if ok { + rute.endpoint.call(res, req, srv.evals, vals) + return + } + } + res.WriteHeader(http.StatusNotFound) +} + func (srv *Server) handleFS( res http.ResponseWriter, req *http.Request, method RequestMethod, ) { @@ -285,24 +372,41 @@ func (srv *Server) handleFS( } } +// +// handleGet handle the GET request by searching the registered route and +// calling the endpoint. +// func (srv *Server) handleGet(res http.ResponseWriter, req *http.Request) { - ep, ok := srv.regGet[req.URL.Path] - if ok { - ep.call(res, req, srv.evals) - return + for _, rute := range srv.routeGets { + vals, ok := rute.parse(req.URL.Path) + if ok { + log.Printf("handleGet: %s %s\n", req.URL.Path, vals) + rute.endpoint.call(res, req, srv.evals, vals) + return + } } srv.handleFS(res, req, RequestMethodGet) } func (srv *Server) handleHead(res http.ResponseWriter, req *http.Request) { - ep, ok := srv.regGet[req.URL.Path] + var ( + rute *route + ok bool + ) + + for _, rute = range srv.routeGets { + _, ok = rute.parse(req.URL.Path) + if ok { + break + } + } if !ok { srv.handleFS(res, req, RequestMethodHead) return } - switch ep.ResponseType { + switch rute.endpoint.ResponseType { case ResponseTypeNone: res.WriteHeader(http.StatusNoContent) return @@ -331,25 +435,44 @@ func (srv *Server) handleOptions(res http.ResponseWriter, req *http.Request) { methods[http.MethodHead] = true } - ep, ok := srv.regDelete[req.URL.Path] - if ok && ep != nil { - methods[http.MethodDelete] = true + for _, rute := range srv.routeDeletes { + _, ok := rute.parse(req.URL.Path) + if ok { + methods[http.MethodDelete] = true + break + } } - _, ok = srv.regGet[req.URL.Path] - if ok && ep != nil { - methods[http.MethodGet] = true + + for _, rute := range srv.routeGets { + _, ok := rute.parse(req.URL.Path) + if ok { + methods[http.MethodGet] = true + break + } } - _, ok = srv.regPatch[req.URL.Path] - if ok && ep != nil { - methods[http.MethodPatch] = true + + for _, rute := range srv.routePatches { + _, ok := rute.parse(req.URL.Path) + if ok { + methods[http.MethodPatch] = true + break + } } - _, ok = srv.regPost[req.URL.Path] - if ok && ep != nil { - methods[http.MethodPost] = true + + for _, rute := range srv.routePosts { + _, ok := rute.parse(req.URL.Path) + if ok { + methods[http.MethodPost] = true + break + } } - ep, ok = srv.regPut[req.URL.Path] - if ok && ep != nil { - methods[http.MethodPut] = true + + for _, rute := range srv.routePuts { + _, ok := rute.parse(req.URL.Path) + if ok { + methods[http.MethodPut] = true + break + } } if len(methods) == 0 { @@ -375,27 +498,46 @@ func (srv *Server) handleOptions(res http.ResponseWriter, req *http.Request) { } // -// register new endpoint with specific method, path, request type, and -// response type. +// handlePatch handle the PATCH request by searching the registered route and +// calling the endpoint. // -func (srv *Server) register(ep *Endpoint) { - if ep == nil || ep.Call == nil { - return +func (srv *Server) handlePatch(res http.ResponseWriter, req *http.Request) { + for _, rute := range srv.routePatches { + vals, ok := rute.parse(req.URL.Path) + if ok { + rute.endpoint.call(res, req, srv.evals, vals) + return + } } - if len(ep.Path) == 0 { - ep.Path = "/" + res.WriteHeader(http.StatusNotFound) +} + +// +// handlePost handle the POST request by searching the registered route and +// calling the endpoint. +// +func (srv *Server) handlePost(res http.ResponseWriter, req *http.Request) { + for _, rute := range srv.routePosts { + vals, ok := rute.parse(req.URL.Path) + if ok { + rute.endpoint.call(res, req, srv.evals, vals) + return + } } + res.WriteHeader(http.StatusNotFound) +} - switch ep.Method { - case RequestMethodDelete: - srv.regDelete[ep.Path] = ep - case RequestMethodGet: - srv.regGet[ep.Path] = ep - case RequestMethodPatch: - srv.regPatch[ep.Path] = ep - case RequestMethodPost: - srv.regPost[ep.Path] = ep - case RequestMethodPut: - srv.regPut[ep.Path] = ep +// +// handlePut handle the PUT request by searching the registered route and +// calling the endpoint. +// +func (srv *Server) handlePut(res http.ResponseWriter, req *http.Request) { + for _, rute := range srv.routePuts { + vals, ok := rute.parse(req.URL.Path) + if ok { + rute.endpoint.call(res, req, srv.evals, vals) + return + } } + res.WriteHeader(http.StatusNotFound) } diff --git a/lib/http/server_test.go b/lib/http/server_test.go index 89055608..1cc9c178 100644 --- a/lib/http/server_test.go +++ b/lib/http/server_test.go @@ -17,78 +17,118 @@ import ( func TestRegisterDelete(t *testing.T) { cases := []struct { - desc string - reqURL string - ep *Endpoint - expStatusCode int - expBody string + desc string + reqURL string + ep *Endpoint + expStatusCode int + expContentType string + expBody string + expError string }{{ - desc: "With unknown path", - reqURL: "http://127.0.0.1:8080/", + desc: "With new endpoint", ep: &Endpoint{ Path: "/delete", ResponseType: ResponseTypePlain, Call: cbPlain, }, - expStatusCode: http.StatusNotFound, }, { - desc: "With known path and subtree root", - reqURL: "http://127.0.0.1:8080/delete/", + desc: "With duplicate endpoint", ep: &Endpoint{ Path: "/delete", ResponseType: ResponseTypePlain, Call: cbPlain, }, + expError: ErrEndpointAmbiguous.Error(), + }, { + desc: "With unknown path", + reqURL: "http://127.0.0.1:8080/", expStatusCode: http.StatusNotFound, }, { - desc: "With response type none", - reqURL: "http://127.0.0.1:8080/delete?k=v", + desc: "With known path and subtree root", + reqURL: "http://127.0.0.1:8080/delete/", + expStatusCode: http.StatusOK, + expContentType: ContentTypePlain, + expBody: "map[]\nmap[]\n<nil>\n", + }, { + desc: "With response type none", ep: &Endpoint{ - Path: "/delete", + Path: "/delete/none", ResponseType: ResponseTypeNone, Call: cbNone, }, + reqURL: "http://127.0.0.1:8080/delete/none?k=v", expStatusCode: http.StatusNoContent, }, { - desc: "With response type binary", - reqURL: "http://127.0.0.1:8080/delete?k=v", + desc: "With response type binary", ep: &Endpoint{ - Path: "/delete", + Path: "/delete/bin", ResponseType: ResponseTypeBinary, Call: cbPlain, }, - expStatusCode: http.StatusOK, - expBody: "map[k:[v]]\nmap[]\n<nil>\n", + reqURL: "http://127.0.0.1:8080/delete/bin?k=v", + expStatusCode: http.StatusOK, + expContentType: ContentTypeBinary, + expBody: "map[k:[v]]\nmap[]\n<nil>\n", }, { - desc: "With response type plain", - reqURL: "http://127.0.0.1:8080/delete?k=v", - ep: &Endpoint{ - Path: "/delete", - ResponseType: ResponseTypePlain, - Call: cbPlain, - }, - expStatusCode: http.StatusOK, - expBody: "map[k:[v]]\nmap[]\n<nil>\n", + desc: "With response type plain", + reqURL: "http://127.0.0.1:8080/delete?k=v", + expStatusCode: http.StatusOK, + expContentType: ContentTypePlain, + expBody: "map[k:[v]]\nmap[]\n<nil>\n", }, { - desc: "With response type JSON", - reqURL: "http://127.0.0.1:8080/delete?k=v", + desc: "With response type JSON", ep: &Endpoint{ - Path: "/delete", + Path: "/delete/json", ResponseType: ResponseTypeJSON, Call: cbJSON, }, - expStatusCode: http.StatusOK, + reqURL: "http://127.0.0.1:8080/delete/json?k=v", + expStatusCode: http.StatusOK, + expContentType: ContentTypeJSON, expBody: `{ "form": "map[k:[v]]", "multipartForm": "<nil>", "body": "" }`, + }, { + desc: "With ambigous path", + ep: &Endpoint{ + Path: "/delete/:id", + ResponseType: ResponseTypePlain, + Call: cbPlain, + }, + expError: ErrEndpointAmbiguous.Error(), + }, { + desc: "With key", + ep: &Endpoint{ + Path: "/delete/:id/x", + ResponseType: ResponseTypePlain, + Call: cbPlain, + }, + reqURL: "http://127.0.0.1:8080/delete/1/x?k=v", + expStatusCode: http.StatusOK, + expContentType: ContentTypePlain, + expBody: "map[id:[1] k:[v]]\nmap[]\n<nil>\n", + }, { + desc: "With duplicate key in query", + reqURL: "http://127.0.0.1:8080/delete/1/x?id=v", + expStatusCode: http.StatusOK, + expContentType: ContentTypePlain, + expBody: "map[id:[1]]\nmap[]\n<nil>\n", }} for _, c := range cases { t.Log(c.desc) - testServer.RegisterDelete(c.ep) + err := testServer.RegisterDelete(c.ep) + if err != nil { + test.Assert(t, "error", c.expError, err.Error(), true) + continue + } + + if len(c.reqURL) == 0 { + continue + } req, e := http.NewRequest(http.MethodDelete, c.reqURL, nil) if e != nil { @@ -118,20 +158,9 @@ func TestRegisterDelete(t *testing.T) { test.Assert(t, "Body", c.expBody, string(body), true) - var expContentType string gotContentType := res.Header.Get(ContentType) - switch c.ep.ResponseType { - case ResponseTypeBinary: - expContentType = ContentTypeBinary - case ResponseTypeJSON: - expContentType = ContentTypeJSON - default: - expContentType = ContentTypePlain - } - - test.Assert(t, "Content-Type", expContentType, gotContentType, - true) + test.Assert(t, "Content-Type", c.expContentType, gotContentType, true) } } @@ -156,7 +185,11 @@ func TestRegisterEvaluator(t *testing.T) { Call: cbPlain, } - testServer.RegisterDelete(epEvaluate) + err := testServer.RegisterDelete(epEvaluate) + if err != nil { + t.Fatal(err) + } + testServer.RegisterEvaluator(testEvaluator) cases := []struct { @@ -202,12 +235,18 @@ func TestRegisterEvaluator(t *testing.T) { } func TestRegisterGet(t *testing.T) { + testServer.evals = nil + epGet := &Endpoint{ Path: "/get", ResponseType: ResponseTypePlain, Call: cbPlain, } - testServer.RegisterGet(epGet) + + err := testServer.RegisterGet(epGet) + if err != nil { + t.Fatal(err) + } cases := []struct { desc string @@ -227,7 +266,8 @@ func TestRegisterGet(t *testing.T) { }, { desc: "With known path and subtree root", reqURL: "http://127.0.0.1:8080/get/", - expStatusCode: http.StatusNotFound, + expStatusCode: http.StatusOK, + expBody: "map[]\nmap[]\n<nil>\n", }, { desc: "With known path", reqURL: "http://127.0.0.1:8080/get?k=v", @@ -258,19 +298,24 @@ func TestRegisterGet(t *testing.T) { t.Fatal(e) } - test.Assert(t, "StatusCode", c.expStatusCode, res.StatusCode, - true) + test.Assert(t, "StatusCode", c.expStatusCode, res.StatusCode, true) test.Assert(t, "Body", c.expBody, string(body), true) } } func TestRegisterHead(t *testing.T) { + testServer.routeGets = nil + epAPI := &Endpoint{ Path: "/api", ResponseType: ResponseTypeJSON, Call: cbNone, } - testServer.RegisterGet(epAPI) + + err := testServer.RegisterGet(epAPI) + if err != nil { + t.Fatal(err) + } cases := []struct { desc string @@ -286,9 +331,10 @@ func TestRegisterHead(t *testing.T) { expContentType: []string{"text/html; charset=utf-8"}, expContentLength: []string{"40"}, }, { - desc: "With registered GET and subtree root", - reqURL: "http://127.0.0.1:8080/api/", - expStatusCode: http.StatusNotFound, + desc: "With registered GET and subtree root", + reqURL: "http://127.0.0.1:8080/api/", + expStatusCode: http.StatusOK, + expContentType: []string{ContentTypeJSON}, }, { desc: "With registered GET", reqURL: "http://127.0.0.1:8080/api?k=v", @@ -336,7 +382,11 @@ func TestRegisterPatch(t *testing.T) { ResponseType: ResponseTypePlain, Call: cbPlain, } - testServer.RegisterPatch(ep) + + err := testServer.RegisterPatch(ep) + if err != nil { + t.Fatal(err) + } cases := []struct { desc string @@ -350,7 +400,8 @@ func TestRegisterPatch(t *testing.T) { }, { desc: "With registered PATCH and subtree root", reqURL: "http://127.0.0.1:8080/patch/", - expStatusCode: http.StatusNotFound, + expStatusCode: http.StatusOK, + expBody: "map[]\nmap[]\n<nil>\n", }, { desc: "With registered PATCH and query", reqURL: "http://127.0.0.1:8080/patch?k=v", @@ -395,7 +446,10 @@ func TestRegisterPost(t *testing.T) { Call: cbPlain, } - testServer.RegisterPost(ep) + err := testServer.RegisterPost(ep) + if err != nil { + t.Fatal(err) + } cases := []struct { desc string @@ -410,7 +464,8 @@ func TestRegisterPost(t *testing.T) { }, { desc: "With registered POST and subtree root", reqURL: "http://127.0.0.1:8080/post/", - expStatusCode: http.StatusNotFound, + expStatusCode: http.StatusOK, + expBody: "map[]\nmap[]\n<nil>\n", }, { desc: "With registered POST and query", reqURL: "http://127.0.0.1:8080/post?k=v", @@ -463,7 +518,10 @@ func TestRegisterPut(t *testing.T) { Call: cbPlain, } - testServer.RegisterPut(ep) + err := testServer.RegisterPut(ep) + if err != nil { + t.Fatal(err) + } cases := []struct { desc string @@ -477,7 +535,7 @@ func TestRegisterPut(t *testing.T) { }, { desc: "With registered PUT and subtree root", reqURL: "http://127.0.0.1:8080/put/", - expStatusCode: http.StatusNotFound, + expStatusCode: http.StatusNoContent, }, { desc: "With registered PUT and query", reqURL: "http://127.0.0.1:8080/put?k=v", @@ -526,8 +584,15 @@ func TestServeHTTPOptions(t *testing.T) { Call: cbPlain, } - testServer.RegisterDelete(epDelete) - testServer.RegisterPatch(epPatch) + err := testServer.RegisterDelete(epDelete) + if err != nil { + t.Fatal(err) + } + + err = testServer.RegisterPatch(epPatch) + if err != nil { + t.Fatal(err) + } cases := []struct { desc string @@ -542,7 +607,8 @@ func TestServeHTTPOptions(t *testing.T) { }, { desc: "With registered PATCH and subtree root", reqURL: "http://127.0.0.1:8080/options/", - expStatusCode: http.StatusNotFound, + expStatusCode: http.StatusOK, + expAllow: "DELETE, OPTIONS, PATCH", }, { desc: "With registered PATCH and query", reqURL: "http://127.0.0.1:8080/options?k=v", @@ -601,7 +667,10 @@ func TestStatusError(t *testing.T) { ResponseType: ResponseTypeNone, Call: cbError, } - testServer.RegisterPost(epErrNoBody) + err := testServer.RegisterPost(epErrNoBody) + if err != nil { + t.Fatal(err) + } epErrBinary := &Endpoint{ Path: "/error/binary", @@ -609,7 +678,10 @@ func TestStatusError(t *testing.T) { ResponseType: ResponseTypeBinary, Call: cbError, } - testServer.RegisterPost(epErrBinary) + err = testServer.RegisterPost(epErrBinary) + if err != nil { + t.Fatal(err) + } epErrJSON := &Endpoint{ Path: "/error/json", @@ -617,7 +689,10 @@ func TestStatusError(t *testing.T) { ResponseType: ResponseTypeJSON, Call: cbError, } - testServer.RegisterPost(epErrJSON) + err = testServer.RegisterPost(epErrJSON) + if err != nil { + t.Fatal(err) + } epErrPlain := &Endpoint{ Path: "/error/plain", @@ -625,7 +700,10 @@ func TestStatusError(t *testing.T) { ResponseType: ResponseTypePlain, Call: cbError, } - testServer.RegisterPost(epErrPlain) + err = testServer.RegisterPost(epErrPlain) + if err != nil { + t.Fatal(err) + } epErrNoCode := &Endpoint{ Path: "/error/no-code", @@ -633,7 +711,10 @@ func TestStatusError(t *testing.T) { ResponseType: ResponseTypePlain, Call: cbNoCode, } - testServer.RegisterPost(epErrNoCode) + err = testServer.RegisterPost(epErrNoCode) + if err != nil { + t.Fatal(err) + } epErrCustom := &Endpoint{ Path: "/error/custom", @@ -641,7 +722,10 @@ func TestStatusError(t *testing.T) { ResponseType: ResponseTypePlain, Call: cbCustomErr, } - testServer.RegisterPost(epErrCustom) + err = testServer.RegisterPost(epErrCustom) + if err != nil { + t.Fatal(err) + } cases := []struct { desc string |
