diff --git a/handler.go b/handler.go index 5ee1a03ce..14ad78dd4 100644 --- a/handler.go +++ b/handler.go @@ -17,7 +17,6 @@ import ( "strings" "time" - "bytes" "github.com/gogo/protobuf/proto" "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" @@ -322,16 +321,6 @@ type sliceMaxResponse struct { // handlePostDB handles POST /db request. func (h *Handler) handlePostDB(w http.ResponseWriter, r *http.Request) { - var err error - // Copy request body for validation - buf, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf)) - r.Body = rdr2 - // Decode request. var req postDBRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -339,15 +328,8 @@ func (h *Handler) handlePostDB(w http.ResponseWriter, r *http.Request) { return } - // Validate request - err = h.validateRequest(buf, r.URL.Path) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - // Create database. - _, err = h.Index.CreateDB(req.DB, req.Options) + _, err := h.Index.CreateDB(req.DB, req.Options) if err == ErrDatabaseExists { http.Error(w, err.Error(), http.StatusConflict) return @@ -362,50 +344,34 @@ func (h *Handler) handlePostDB(w http.ResponseWriter, r *http.Request) { } } -// Validate request body for db/frame creation -func (h *Handler) validateRequest(r []byte, path string) error { +// Custom Unmarshal JSON to validate request body when creating a new database +func (p *postDBRequest) UnmarshalJSON(b []byte) error { var data map[string]interface{} - - err := json.Unmarshal(r, &data) - if err != nil { + if err := json.Unmarshal(b, &data); err != nil { return err } - - option, ok := data["options"] - if len(data) >= 2 && !ok { - return errors.New("options needs to be provided") - } else if ok { - err = h.validateOptions(path, option.(map[string]interface{})) - if err != nil { - return fmt.Errorf("invalid options: %s", option.(map[string]interface{})) - } + f := func(key string, m map[string]interface{}) bool { _, ok := m[key]; return ok } + if !f("db", data) { + return errors.New("db required") } + p.DB = data["db"].(string) - return nil - -} - -// Validate options in request body for db/frame creation, make sure key and value for columnLabel/rowLable is correct -func (h Handler) validateOptions(path string, options map[string]interface{}) error { - switch path { - case "/db": - if _, ok := options["columnLabel"]; !ok && len(options) > 0 { - return errors.New("columnLabel is not provided") - } else if ok { + if f("options", data) { + options := data["options"].(map[string]interface{}) + if len(options) == 0 { + return nil + } else if f("columnLabel", options) { err := ValidateName(options["columnLabel"].(string)) if err != nil { - return err - } - } - case "/frame": - if _, ok := options["rowLabel"]; !ok && len(options) > 0 { - return errors.New("rowLabel is not provided") - } else if ok { - err := ValidateName(options["rowLabel"].(string)) - if err != nil { - return err + return errors.New("invalid columnLabel") } + p.Options = DBOptions{ColumnLabel: options["columnLabel"].(string)} + } else { + return errors.New("columnLabel required") } + + } else if len(data) > 1 { + return errors.New("options required") } return nil } @@ -544,22 +510,6 @@ type postDBAttrDiffResponse struct { // handlePostFrame handles POST /frame request. func (h *Handler) handlePostFrame(w http.ResponseWriter, r *http.Request) { - var err error - // Copy request body for validation - buf, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf)) - r.Body = rdr2 - - // Validate request - err = h.validateRequest(buf, r.URL.Path) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } // Decode request. var req postFrameRequest @@ -576,7 +526,7 @@ func (h *Handler) handlePostFrame(w http.ResponseWriter, r *http.Request) { } // Create frame. - _, err = db.CreateFrame(req.Frame, req.Options) + _, err := db.CreateFrame(req.Frame, req.Options) if err == ErrFrameExists { http.Error(w, err.Error(), http.StatusConflict) return @@ -591,6 +541,43 @@ func (h *Handler) handlePostFrame(w http.ResponseWriter, r *http.Request) { } } +// Custom Unmarshal JSON to validate request body when creating a new frame +func (p *postFrameRequest) UnmarshalJSON(b []byte) error { + var data map[string]interface{} + if err := json.Unmarshal(b, &data); err != nil { + return err + } + f := func(key string, m map[string]interface{}) bool { _, ok := m[key]; return ok } + if !f("db", data) { + return errors.New("db required") + } + p.DB = data["db"].(string) + + if !f("frame", data) { + return errors.New("frame required") + } + p.Frame = data["frame"].(string) + + if f("options", data) { + options := data["options"].(map[string]interface{}) + if len(options) == 0 { + return nil + } else if f("rowLabel", options) { + err := ValidateName(options["rowLabel"].(string)) + if err != nil { + return errors.New("invalid rowLabel") + } + p.Options = FrameOptions{RowLabel: options["rowLabel"].(string)} + } else { + return errors.New("rowLabel required") + } + + } else if len(data) > 2 { + return errors.New("options required") + } + return nil +} + type postFrameRequest struct { DB string `json:"db"` Frame string `json:"frame"` diff --git a/handler_test.go b/handler_test.go index c6d181f44..007bb639a 100644 --- a/handler_test.go +++ b/handler_test.go @@ -14,7 +14,6 @@ import ( "strings" "testing" - "fmt" "github.com/gogo/protobuf/proto" "github.com/pilosa/pilosa" "github.com/pilosa/pilosa/internal" @@ -890,8 +889,7 @@ func TestHandler_DB_Options(t *testing.T) { t.Fatalf("unexpected status: %d", resp.StatusCode) } else if buf, err := ioutil.ReadAll(resp.Body); err != nil { t.Fatal(err) - } else if string(buf) != "options needs to be provided"+"\n" { - fmt.Println(string(buf) == "options needs to be provided") + } else if string(buf) != "options required"+"\n" { t.Fatalf("unexpected response body: %s", buf) } @@ -921,7 +919,7 @@ func TestHandler_Frame_Options(t *testing.T) { t.Fatalf("unexpected status: %d", resp.StatusCode) } else if buf, err := ioutil.ReadAll(resp.Body); err != nil { t.Fatal(err) - } else if string(buf) != "invalid options: map[columnLabel:location]"+"\n" { + } else if string(buf) != "rowLabel required"+"\n" { t.Fatalf("unexpected response body: %s", buf) } } @@ -939,7 +937,7 @@ func TestHandler_OptionsValue(t *testing.T) { if _, err := idx.CreateDBIfNotExists("sample-db", pilosa.DBOptions{}); err != nil { t.Fatal(err) } - resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/frame", strings.NewReader(`{"db": "sample-db", "options": {"rowLabel": "///"}}`))) + resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/frame", strings.NewReader(`{"db": "sample-db", "frame": "test", "options": {"rowLabel": "///"}}`))) if err != nil { t.Fatal(err) } @@ -950,7 +948,7 @@ func TestHandler_OptionsValue(t *testing.T) { t.Fatalf("unexpected status: %d", resp.StatusCode) } else if buf, err := ioutil.ReadAll(resp.Body); err != nil { t.Fatal(err) - } else if string(buf) != "invalid options: map[rowLabel:///]"+"\n" { + } else if string(buf) != "invalid rowLabel"+"\n" { t.Fatalf("unexpected response body: %s", buf) } }