2) get services JSON errors

This commit is contained in:
Matthew Dillon 2015-06-24 17:14:04 -08:00
parent cf6dcb6a7b
commit 4a1d968539
9 changed files with 131 additions and 67 deletions

View file

@ -1,14 +1,21 @@
package main package main
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
) )
var (
ErrCharacteristicTypeNotFound = errors.New("Characteristic Type not found")
ErrCharacteristicTypeNotFoundJSON = newJSONError(ErrCharacteristicTypeNotFound, http.StatusNotFound)
)
func init() { func init() {
DB.AddTableWithName(CharacteristicTypeBase{}, "characteristic_types").SetKeys(true, "Id") 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 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 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 FROM characteristic_types ct
INNER JOIN characteristics c ON c.characteristic_type_id=ct.id INNER JOIN characteristics c ON c.characteristic_type_id=ct.id
WHERE ct.id=$1 WHERE ct.id=$1
GROUP BY ct.id;` GROUP BY ct.id;`
if err := DBH.SelectOne(&characteristic_type, sql, id); err != nil { if err := DBH.SelectOne(&characteristic_type, q, id); err != nil {
return nil, err if err == sql.ErrNoRows {
return nil, ErrCharacteristicTypeNotFoundJSON
}
return nil, newJSONError(err, http.StatusInternalServerError)
} }
return &characteristic_type, nil return &characteristic_type, nil
} }

View file

@ -1,14 +1,21 @@
package main package main
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
) )
var (
ErrCharacteristicNotFound = errors.New("Characteristic not found")
ErrCharacteristicNotFoundJSON = newJSONError(ErrCharacteristicNotFound, http.StatusNotFound)
)
func init() { func init() {
DB.AddTableWithName(CharacteristicBase{}, "characteristics").SetKeys(true, "Id") DB.AddTableWithName(CharacteristicBase{}, "characteristics").SetKeys(true, "Id")
} }
@ -91,16 +98,19 @@ func (c CharacteristicService) list(val *url.Values) (entity, error) {
return &characteristics, nil 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 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 FROM characteristics c
LEFT OUTER JOIN measurements m ON m.characteristic_id=c.id LEFT OUTER JOIN measurements m ON m.characteristic_id=c.id
LEFT OUTER JOIN strains st ON st.id=m.strain_id LEFT OUTER JOIN strains st ON st.id=m.strain_id
WHERE c.id=$1 WHERE c.id=$1
GROUP BY c.id;` GROUP BY c.id;`
if err := DBH.SelectOne(&characteristic, sql, id); err != nil { if err := DBH.SelectOne(&characteristic, q, id); err != nil {
return nil, err if err == sql.ErrNoRows {
return nil, ErrCharacteristicNotFoundJSON
}
return nil, newJSONError(err, http.StatusInternalServerError)
} }
return &characteristic, nil return &characteristic, nil
} }

View file

@ -7,7 +7,7 @@ type entity interface {
} }
type getter interface { type getter interface {
get(int64, string) (entity, error) get(int64, string) (entity, *appError)
} }
type lister interface { type lister interface {

View file

@ -68,74 +68,73 @@ func Handler() http.Handler {
} }
m := mux.NewRouter() m := mux.NewRouter()
userService := UserService{}
strainService := StrainService{}
speciesService := SpeciesService{}
characteristicService := CharacteristicService{}
characteristicTypeService := CharacteristicTypeService{}
measurementService := MeasurementService{}
// Non-auth routes // Non-auth routes
m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST") m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST")
// Auth routes // Auth routes
m.Handle("/users", j.Secure(http.HandlerFunc(handleLister(UserService{})), verifyClaims)).Methods("GET") 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", 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(errorHandler(handleGetter(userService)), verifyClaims)).Methods("GET")
m.Handle("/users/{Id:.+}", j.Secure(http.HandlerFunc(handleUpdater(UserService{})), verifyClaims)).Methods("PUT") m.Handle("/users/{Id:.+}", j.Secure(http.HandlerFunc(handleUpdater(userService)), verifyClaims)).Methods("PUT")
// Path-based pattern matching subrouter // Path-based pattern matching subrouter
s := m.PathPrefix("/{genus}").Subrouter() s := m.PathPrefix("/{genus}").Subrouter()
type r struct { type r struct {
f http.HandlerFunc f errorHandler
m string m string
p string p string
} }
routes := []r{ routes := []r{
r{handleLister(StrainService{}), "GET", "/strains"}, // r{handleLister(speciesService), "GET", "/species"},
r{handleCreater(StrainService{}), "POST", "/strains"}, // r{handleCreater(speciesService), "POST", "/species"},
r{handleGetter(StrainService{}), "GET", "/strains/{Id:.+}"}, r{handleGetter(speciesService), "GET", "/species/{Id:.+}"},
r{handleUpdater(StrainService{}), "PUT", "/strains/{Id:.+}"}, // r{handleUpdater(speciesService), "PUT", "/species/{Id:.+}"},
r{handleLister(MeasurementService{}), "GET", "/measurements"}, // r{handleLister(strainService), "GET", "/strains"},
r{handleGetter(MeasurementService{}), "GET", "/measurements/{Id:.+}"}, // r{handleCreater(strainService), "POST", "/strains"},
r{handleLister(CharacteristicService{}), "GET", "/characteristics"}, r{handleGetter(strainService), "GET", "/strains/{Id:.+}"},
r{handleGetter(CharacteristicService{}), "GET", "/characteristics/{Id:.+}"}, // r{handleUpdater(strainService), "PUT", "/strains/{Id:.+}"},
r{handleLister(SpeciesService{}), "GET", "/species"}, // r{handleLister(characteristicService), "GET", "/characteristics"},
r{handleCreater(SpeciesService{}), "POST", "/species"}, r{handleGetter(characteristicService), "GET", "/characteristics/{Id:.+}"},
r{handleGetter(SpeciesService{}), "GET", "/species/{Id:.+}"}, // r{handleLister(characteristicTypeService), "GET", "/characteristicTypes"},
r{handleUpdater(SpeciesService{}), "PUT", "/species/{Id:.+}"}, r{handleGetter(characteristicTypeService), "GET", "/characteristicTypes/{Id:.+}"},
r{handleLister(CharacteristicTypeService{}), "GET", "/characteristicTypes"}, // r{handleLister(measurementService), "GET", "/measurements"},
r{handleGetter(CharacteristicTypeService{}), "GET", "/characteristicTypes/{Id:.+}"}, r{handleGetter(measurementService), "GET", "/measurements/{Id:.+}"},
} }
for _, route := range routes { 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)) return jsonHandler(corsHandler(m))
} }
func Error(w http.ResponseWriter, err string, code int) { func handleGetter(g getter) errorHandler {
w.WriteHeader(code) return func(w http.ResponseWriter, r *http.Request) *appError {
fmt.Fprintln(w, err)
}
func handleGetter(g getter) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) return newJSONError(err, http.StatusInternalServerError)
return
} }
e, err := g.get(id, mux.Vars(r)["genus"]) e, appErr := g.get(id, mux.Vars(r)["genus"])
if err != nil { if appErr != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) return appErr
return
} }
data, err := e.marshal() data, err := e.marshal()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) return newJSONError(err, http.StatusInternalServerError)
return
} }
w.Write(data) w.Write(data)
return nil
} }
} }
@ -214,7 +213,7 @@ func handleCreater(c creater) http.HandlerFunc {
err = c.create(&e, claims) err = c.create(&e, claims)
if err != nil { if err != nil {
Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -264,3 +263,12 @@ func jsonHandler(h http.Handler) http.Handler {
} }
return http.HandlerFunc(j) 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())
}
}

View file

@ -1,13 +1,18 @@
package main package main
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http"
"net/url" "net/url"
"time" "time"
) )
var ErrMeasurementNotFound = errors.New("measurement not found") var (
ErrMeasurementNotFound = errors.New("Measurement not found")
ErrMeasurementNotFoundJSON = newJSONError(ErrMeasurementNotFound, http.StatusNotFound)
)
func init() { func init() {
DB.AddTableWithName(MeasurementBase{}, "measurements").SetKeys(true, "Id") DB.AddTableWithName(MeasurementBase{}, "measurements").SetKeys(true, "Id")
@ -134,9 +139,9 @@ func (m MeasurementService) list(val *url.Values) (entity, error) {
return &measurements, nil 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 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 u.symbol AS unit_type_name, te.name AS test_method_name
FROM measurements m FROM measurements m
INNER JOIN strains st ON st.id=m.strain_id 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 unit_types u ON u.id=m.unit_type_id
LEFT OUTER JOIN test_methods te ON te.id=m.test_method_id LEFT OUTER JOIN test_methods te ON te.id=m.test_method_id
WHERE m.id=$2;` WHERE m.id=$2;`
if err := DBH.SelectOne(&measurement, sql, genus, id); err != nil { if err := DBH.SelectOne(&measurement, q, genus, id); err != nil {
return nil, err if err == sql.ErrNoRows {
return nil, ErrMeasurementNotFoundJSON
} }
if &measurement == nil { return nil, newJSONError(err, http.StatusInternalServerError)
return nil, ErrMeasurementNotFound
} }
return &measurement, nil return &measurement, nil
} }

View file

@ -5,14 +5,16 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
) )
var ( var (
ErrSpeciesNotFound = errors.New("species not found") ErrSpeciesNotFound = errors.New("Species not found")
ErrSpeciesNotUpdated = errors.New("species not updated") ErrSpeciesNotFoundJSON = newJSONError(ErrSpeciesNotFound, http.StatusNotFound)
ErrSpeciesNotUpdated = errors.New("Species not updated")
) )
func init() { func init() {
@ -110,7 +112,7 @@ func (s SpeciesService) list(val *url.Values) (entity, error) {
return &species, nil 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 var species Species
q := `SELECT sp.*, g.genus_name, array_agg(st.id) AS strains, q := `SELECT sp.*, g.genus_name, array_agg(st.id) AS strains,
COUNT(st) AS total_strains, 0 AS sort_order 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;` GROUP BY sp.id, g.genus_name;`
if err := DBH.SelectOne(&species, q, genus, id); err != nil { if err := DBH.SelectOne(&species, q, genus, id); err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, ErrStrainNotFound return nil, ErrSpeciesNotFoundJSON
} }
return nil, err return nil, newJSONError(err, http.StatusInternalServerError)
} }
return &species, nil return &species, nil
} }

View file

@ -5,14 +5,16 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
) )
var ( var (
ErrStrainNotFound = errors.New("strain not found") ErrStrainNotFound = errors.New("Strain not found")
ErrStrainNotUpdated = errors.New("strain not updated") ErrStrainNotFoundJSON = newJSONError(ErrStrainNotFound, http.StatusNotFound)
ErrStrainNotUpdated = errors.New("Strain not updated")
) )
func init() { func init() {
@ -112,7 +114,7 @@ func (s StrainService) list(val *url.Values) (entity, error) {
return &strains, nil 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 var strain Strain
q := `SELECT st.*, array_agg(m.id) AS measurements, COUNT(m) AS total_measurements, q := `SELECT st.*, array_agg(m.id) AS measurements, COUNT(m) AS total_measurements,
0 AS sort_order 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;` GROUP BY st.id, st.species_id;`
if err := DBH.SelectOne(&strain, q, genus, id); err != nil { if err := DBH.SelectOne(&strain, q, genus, id); err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, ErrStrainNotFound return nil, ErrStrainNotFoundJSON
} }
return nil, err return nil, newJSONError(err, http.StatusInternalServerError)
} }
return &strain, nil return &strain, nil
} }

View file

@ -186,3 +186,28 @@ func strToIntSlice(s string) []int64 {
} }
return a 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,
}
}

View file

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http"
"net/url" "net/url"
"time" "time"
@ -11,7 +12,8 @@ import (
) )
var ( 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") ErrInvalidEmailOrPassword = errors.New("Invalid email or password")
) )
@ -104,14 +106,14 @@ func (u UserService) list(val *url.Values) (entity, error) {
return &users, nil 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 var user User
q := `SELECT * FROM users WHERE id=$1;` q := `SELECT * FROM users WHERE id=$1;`
if err := DBH.SelectOne(&user, q, id); err != nil { if err := DBH.SelectOne(&user, q, id); err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, ErrUserNotFound return nil, ErrUserNotFoundJSON
} }
return nil, err return nil, newJSONError(err, http.StatusInternalServerError)
} }
return &user, nil return &user, nil
} }