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
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
}

View file

@ -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
}

View file

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

View file

@ -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())
}
}

View file

@ -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 err := DBH.SelectOne(&measurement, q, genus, id); err != nil {
if err == sql.ErrNoRows {
return nil, ErrMeasurementNotFoundJSON
}
if &measurement == nil {
return nil, ErrMeasurementNotFound
return nil, newJSONError(err, http.StatusInternalServerError)
}
return &measurement, nil
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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,
}
}

View file

@ -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
}