From 4a1d9685396969ab4abb37ddf6c6a5df59db4f43 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Wed, 24 Jun 2015 17:14:04 -0800 Subject: [PATCH] 2) get services JSON errors --- characteristic_types.go | 18 +++++++--- characteristics.go | 18 +++++++--- entities.go | 2 +- handlers.go | 80 ++++++++++++++++++++++------------------- measurements.go | 21 ++++++----- species.go | 12 ++++--- strains.go | 12 ++++--- types.go | 25 +++++++++++++ users.go | 10 +++--- 9 files changed, 131 insertions(+), 67 deletions(-) diff --git a/characteristic_types.go b/characteristic_types.go index 5cce602..b1d2088 100644 --- a/characteristic_types.go +++ b/characteristic_types.go @@ -1,14 +1,21 @@ package main import ( + "database/sql" "encoding/json" "errors" "fmt" + "net/http" "net/url" "strings" "time" ) +var ( + ErrCharacteristicTypeNotFound = errors.New("Characteristic Type not found") + ErrCharacteristicTypeNotFoundJSON = newJSONError(ErrCharacteristicTypeNotFound, http.StatusNotFound) +) + func init() { DB.AddTableWithName(CharacteristicTypeBase{}, "characteristic_types").SetKeys(true, "Id") } @@ -89,15 +96,18 @@ func (c CharacteristicTypeService) list(val *url.Values) (entity, error) { return &characteristic_types, nil } -func (c CharacteristicTypeService) get(id int64, dummy string) (entity, error) { +func (c CharacteristicTypeService) get(id int64, dummy string) (entity, *appError) { var characteristic_type CharacteristicType - sql := `SELECT ct.*, array_agg(c.id) AS characteristics, 0 AS sort_order + q := `SELECT ct.*, array_agg(c.id) AS characteristics, 0 AS sort_order FROM characteristic_types ct INNER JOIN characteristics c ON c.characteristic_type_id=ct.id WHERE ct.id=$1 GROUP BY ct.id;` - if err := DBH.SelectOne(&characteristic_type, sql, id); err != nil { - return nil, err + if err := DBH.SelectOne(&characteristic_type, q, id); err != nil { + if err == sql.ErrNoRows { + return nil, ErrCharacteristicTypeNotFoundJSON + } + return nil, newJSONError(err, http.StatusInternalServerError) } return &characteristic_type, nil } diff --git a/characteristics.go b/characteristics.go index b323c32..2457534 100644 --- a/characteristics.go +++ b/characteristics.go @@ -1,14 +1,21 @@ package main import ( + "database/sql" "encoding/json" "errors" "fmt" + "net/http" "net/url" "strings" "time" ) +var ( + ErrCharacteristicNotFound = errors.New("Characteristic not found") + ErrCharacteristicNotFoundJSON = newJSONError(ErrCharacteristicNotFound, http.StatusNotFound) +) + func init() { DB.AddTableWithName(CharacteristicBase{}, "characteristics").SetKeys(true, "Id") } @@ -91,16 +98,19 @@ func (c CharacteristicService) list(val *url.Values) (entity, error) { return &characteristics, nil } -func (c CharacteristicService) get(id int64, dummy string) (entity, error) { +func (c CharacteristicService) get(id int64, dummy string) (entity, *appError) { var characteristic Characteristic - sql := `SELECT c.*, array_agg(m.id) AS measurements, array_agg(st.id) AS strains + q := `SELECT c.*, array_agg(m.id) AS measurements, array_agg(st.id) AS strains FROM characteristics c LEFT OUTER JOIN measurements m ON m.characteristic_id=c.id LEFT OUTER JOIN strains st ON st.id=m.strain_id WHERE c.id=$1 GROUP BY c.id;` - if err := DBH.SelectOne(&characteristic, sql, id); err != nil { - return nil, err + if err := DBH.SelectOne(&characteristic, q, id); err != nil { + if err == sql.ErrNoRows { + return nil, ErrCharacteristicNotFoundJSON + } + return nil, newJSONError(err, http.StatusInternalServerError) } return &characteristic, nil } diff --git a/entities.go b/entities.go index 66a09e4..1af85c0 100644 --- a/entities.go +++ b/entities.go @@ -7,7 +7,7 @@ type entity interface { } type getter interface { - get(int64, string) (entity, error) + get(int64, string) (entity, *appError) } type lister interface { diff --git a/handlers.go b/handlers.go index 5ea7d58..80b6bb1 100644 --- a/handlers.go +++ b/handlers.go @@ -68,74 +68,73 @@ func Handler() http.Handler { } m := mux.NewRouter() + userService := UserService{} + strainService := StrainService{} + speciesService := SpeciesService{} + characteristicService := CharacteristicService{} + characteristicTypeService := CharacteristicTypeService{} + measurementService := MeasurementService{} // Non-auth routes m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST") // Auth routes - m.Handle("/users", j.Secure(http.HandlerFunc(handleLister(UserService{})), verifyClaims)).Methods("GET") - m.Handle("/users", j.Secure(http.HandlerFunc(handleCreater(UserService{})), verifyClaims)).Methods("POST") - m.Handle("/users/{Id:.+}", j.Secure(http.HandlerFunc(handleGetter(UserService{})), verifyClaims)).Methods("GET") - m.Handle("/users/{Id:.+}", j.Secure(http.HandlerFunc(handleUpdater(UserService{})), verifyClaims)).Methods("PUT") + m.Handle("/users", j.Secure(http.HandlerFunc(handleLister(userService)), verifyClaims)).Methods("GET") + m.Handle("/users", j.Secure(http.HandlerFunc(handleCreater(userService)), verifyClaims)).Methods("POST") + m.Handle("/users/{Id:.+}", j.Secure(errorHandler(handleGetter(userService)), verifyClaims)).Methods("GET") + m.Handle("/users/{Id:.+}", j.Secure(http.HandlerFunc(handleUpdater(userService)), verifyClaims)).Methods("PUT") // Path-based pattern matching subrouter s := m.PathPrefix("/{genus}").Subrouter() type r struct { - f http.HandlerFunc + f errorHandler m string p string } routes := []r{ - r{handleLister(StrainService{}), "GET", "/strains"}, - r{handleCreater(StrainService{}), "POST", "/strains"}, - r{handleGetter(StrainService{}), "GET", "/strains/{Id:.+}"}, - r{handleUpdater(StrainService{}), "PUT", "/strains/{Id:.+}"}, - r{handleLister(MeasurementService{}), "GET", "/measurements"}, - r{handleGetter(MeasurementService{}), "GET", "/measurements/{Id:.+}"}, - r{handleLister(CharacteristicService{}), "GET", "/characteristics"}, - r{handleGetter(CharacteristicService{}), "GET", "/characteristics/{Id:.+}"}, - r{handleLister(SpeciesService{}), "GET", "/species"}, - r{handleCreater(SpeciesService{}), "POST", "/species"}, - r{handleGetter(SpeciesService{}), "GET", "/species/{Id:.+}"}, - r{handleUpdater(SpeciesService{}), "PUT", "/species/{Id:.+}"}, - r{handleLister(CharacteristicTypeService{}), "GET", "/characteristicTypes"}, - r{handleGetter(CharacteristicTypeService{}), "GET", "/characteristicTypes/{Id:.+}"}, + // r{handleLister(speciesService), "GET", "/species"}, + // r{handleCreater(speciesService), "POST", "/species"}, + r{handleGetter(speciesService), "GET", "/species/{Id:.+}"}, + // r{handleUpdater(speciesService), "PUT", "/species/{Id:.+}"}, + // r{handleLister(strainService), "GET", "/strains"}, + // r{handleCreater(strainService), "POST", "/strains"}, + r{handleGetter(strainService), "GET", "/strains/{Id:.+}"}, + // r{handleUpdater(strainService), "PUT", "/strains/{Id:.+}"}, + // r{handleLister(characteristicService), "GET", "/characteristics"}, + r{handleGetter(characteristicService), "GET", "/characteristics/{Id:.+}"}, + // r{handleLister(characteristicTypeService), "GET", "/characteristicTypes"}, + r{handleGetter(characteristicTypeService), "GET", "/characteristicTypes/{Id:.+}"}, + // r{handleLister(measurementService), "GET", "/measurements"}, + r{handleGetter(measurementService), "GET", "/measurements/{Id:.+}"}, } for _, route := range routes { - s.Handle(route.p, j.Secure(http.HandlerFunc(route.f), verifyClaims)).Methods(route.m) + s.Handle(route.p, j.Secure(errorHandler(route.f), verifyClaims)).Methods(route.m) } return jsonHandler(corsHandler(m)) } -func Error(w http.ResponseWriter, err string, code int) { - w.WriteHeader(code) - fmt.Fprintln(w, err) -} - -func handleGetter(g getter) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func handleGetter(g getter) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *appError { id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return newJSONError(err, http.StatusInternalServerError) } - e, err := g.get(id, mux.Vars(r)["genus"]) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + e, appErr := g.get(id, mux.Vars(r)["genus"]) + if appErr != nil { + return appErr } data, err := e.marshal() if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return newJSONError(err, http.StatusInternalServerError) } w.Write(data) + return nil } } @@ -214,7 +213,7 @@ func handleCreater(c creater) http.HandlerFunc { err = c.create(&e, claims) if err != nil { - Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -264,3 +263,12 @@ func jsonHandler(h http.Handler) http.Handler { } return http.HandlerFunc(j) } + +type errorHandler func(http.ResponseWriter, *http.Request) *appError + +func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + w.WriteHeader(err.Status) + fmt.Fprintln(w, err.Error.Error()) + } +} diff --git a/measurements.go b/measurements.go index ec1082e..2f3d888 100644 --- a/measurements.go +++ b/measurements.go @@ -1,13 +1,18 @@ package main import ( + "database/sql" "encoding/json" "errors" + "net/http" "net/url" "time" ) -var ErrMeasurementNotFound = errors.New("measurement not found") +var ( + ErrMeasurementNotFound = errors.New("Measurement not found") + ErrMeasurementNotFoundJSON = newJSONError(ErrMeasurementNotFound, http.StatusNotFound) +) func init() { DB.AddTableWithName(MeasurementBase{}, "measurements").SetKeys(true, "Id") @@ -134,9 +139,9 @@ func (m MeasurementService) list(val *url.Values) (entity, error) { return &measurements, nil } -func (m MeasurementService) get(id int64, genus string) (entity, error) { +func (m MeasurementService) get(id int64, genus string) (entity, *appError) { var measurement Measurement - sql := `SELECT m.*, t.text_measurement_name AS text_measurement_type_name, + q := `SELECT m.*, t.text_measurement_name AS text_measurement_type_name, u.symbol AS unit_type_name, te.name AS test_method_name FROM measurements m INNER JOIN strains st ON st.id=m.strain_id @@ -147,11 +152,11 @@ func (m MeasurementService) get(id int64, genus string) (entity, error) { LEFT OUTER JOIN unit_types u ON u.id=m.unit_type_id LEFT OUTER JOIN test_methods te ON te.id=m.test_method_id WHERE m.id=$2;` - if err := DBH.SelectOne(&measurement, sql, genus, id); err != nil { - return nil, err - } - if &measurement == nil { - return nil, ErrMeasurementNotFound + if err := DBH.SelectOne(&measurement, q, genus, id); err != nil { + if err == sql.ErrNoRows { + return nil, ErrMeasurementNotFoundJSON + } + return nil, newJSONError(err, http.StatusInternalServerError) } return &measurement, nil } diff --git a/species.go b/species.go index b5dca92..ff7fa38 100644 --- a/species.go +++ b/species.go @@ -5,14 +5,16 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "net/url" "strings" "time" ) var ( - ErrSpeciesNotFound = errors.New("species not found") - ErrSpeciesNotUpdated = errors.New("species not updated") + ErrSpeciesNotFound = errors.New("Species not found") + ErrSpeciesNotFoundJSON = newJSONError(ErrSpeciesNotFound, http.StatusNotFound) + ErrSpeciesNotUpdated = errors.New("Species not updated") ) func init() { @@ -110,7 +112,7 @@ func (s SpeciesService) list(val *url.Values) (entity, error) { return &species, nil } -func (s SpeciesService) get(id int64, genus string) (entity, error) { +func (s SpeciesService) get(id int64, genus string) (entity, *appError) { var species Species q := `SELECT sp.*, g.genus_name, array_agg(st.id) AS strains, COUNT(st) AS total_strains, 0 AS sort_order @@ -121,9 +123,9 @@ func (s SpeciesService) get(id int64, genus string) (entity, error) { GROUP BY sp.id, g.genus_name;` if err := DBH.SelectOne(&species, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, ErrStrainNotFound + return nil, ErrSpeciesNotFoundJSON } - return nil, err + return nil, newJSONError(err, http.StatusInternalServerError) } return &species, nil } diff --git a/strains.go b/strains.go index d25e9dc..cb88da3 100644 --- a/strains.go +++ b/strains.go @@ -5,14 +5,16 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "net/url" "strings" "time" ) var ( - ErrStrainNotFound = errors.New("strain not found") - ErrStrainNotUpdated = errors.New("strain not updated") + ErrStrainNotFound = errors.New("Strain not found") + ErrStrainNotFoundJSON = newJSONError(ErrStrainNotFound, http.StatusNotFound) + ErrStrainNotUpdated = errors.New("Strain not updated") ) func init() { @@ -112,7 +114,7 @@ func (s StrainService) list(val *url.Values) (entity, error) { return &strains, nil } -func (s StrainService) get(id int64, genus string) (entity, error) { +func (s StrainService) get(id int64, genus string) (entity, *appError) { var strain Strain q := `SELECT st.*, array_agg(m.id) AS measurements, COUNT(m) AS total_measurements, 0 AS sort_order @@ -124,9 +126,9 @@ func (s StrainService) get(id int64, genus string) (entity, error) { GROUP BY st.id, st.species_id;` if err := DBH.SelectOne(&strain, q, genus, id); err != nil { if err == sql.ErrNoRows { - return nil, ErrStrainNotFound + return nil, ErrStrainNotFoundJSON } - return nil, err + return nil, newJSONError(err, http.StatusInternalServerError) } return &strain, nil } diff --git a/types.go b/types.go index 80f5259..9519a5c 100644 --- a/types.go +++ b/types.go @@ -186,3 +186,28 @@ func strToIntSlice(s string) []int64 { } return a } + +type ErrorJSON struct { + Err error +} + +func (ej ErrorJSON) Error() string { + e, _ := json.Marshal(struct { + Err string `json:"error"` + }{ + Err: ej.Err.Error(), + }) + return string(e) +} + +type appError struct { + Error error + Status int +} + +func newJSONError(err error, status int) *appError { + return &appError{ + Error: ErrorJSON{Err: err}, + Status: status, + } +} diff --git a/users.go b/users.go index d119171..ea9e8c0 100644 --- a/users.go +++ b/users.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "errors" + "net/http" "net/url" "time" @@ -11,7 +12,8 @@ import ( ) var ( - ErrUserNotFound = errors.New("user not found") + ErrUserNotFound = errors.New("User not found") + ErrUserNotFoundJSON = newJSONError(ErrUserNotFound, http.StatusNotFound) ErrInvalidEmailOrPassword = errors.New("Invalid email or password") ) @@ -104,14 +106,14 @@ func (u UserService) list(val *url.Values) (entity, error) { return &users, nil } -func (u UserService) get(id int64, genus string) (entity, error) { +func (u UserService) get(id int64, genus string) (entity, *appError) { var user User q := `SELECT * FROM users WHERE id=$1;` if err := DBH.SelectOne(&user, q, id); err != nil { if err == sql.ErrNoRows { - return nil, ErrUserNotFound + return nil, ErrUserNotFoundJSON } - return nil, err + return nil, newJSONError(err, http.StatusInternalServerError) } return &user, nil }