changeset 237:3771788d3dae

Reduce boilerplate code when writing JSON parsing/generating endpoints.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 26 Jul 2018 17:07:03 +0200
parents 664fe6536141
children 2b39bf2bf1fd
files auth/middleware.go controllers/json.go controllers/routes.go controllers/user.go
diffstat 4 files changed, 182 insertions(+), 122 deletions(-) [+]
line wrap: on
line diff
--- a/auth/middleware.go	Thu Jul 26 15:43:19 2018 +0200
+++ b/auth/middleware.go	Thu Jul 26 17:07:03 2018 +0200
@@ -74,8 +74,8 @@
 	}
 }
 
-func EnsureRole(roles ...string) func(func(http.ResponseWriter, *http.Request)) http.Handler {
-	return func(fn func(http.ResponseWriter, *http.Request)) http.Handler {
-		return SessionMiddleware(SessionChecker(http.HandlerFunc(fn), HasRole(roles...)))
+func EnsureRole(roles ...string) func(http.Handler) http.Handler {
+	return func(handler http.Handler) http.Handler {
+		return SessionMiddleware(SessionChecker(handler, HasRole(roles...)))
 	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controllers/json.go	Thu Jul 26 17:07:03 2018 +0200
@@ -0,0 +1,94 @@
+package controllers
+
+import (
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"log"
+	"net/http"
+
+	"gemma.intevation.de/gemma/auth"
+	"github.com/jackc/pgx"
+)
+
+type JSONResult struct {
+	Code   int
+	Result interface{}
+}
+
+type JSONHandler struct {
+	Input   func() interface{}
+	Process func(http.ResponseWriter, *http.Request, interface{}, *sql.DB) (JSONResult, error)
+}
+
+type JSONError struct {
+	Code    int
+	Message string
+}
+
+func (je JSONError) Error() string {
+	return fmt.Sprintf("%d: %s", je.Code, je.Message)
+}
+
+func (j *JSONHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+
+	var input interface{}
+	if j.Input != nil {
+		input = j.Input()
+		defer req.Body.Close()
+		if err := json.NewDecoder(req.Body).Decode(input); err != nil {
+			http.Error(rw, "error: "+err.Error(), http.StatusBadRequest)
+			return
+		}
+	}
+
+	token, _ := auth.GetToken(req)
+	var jr JSONResult
+	err := auth.ConnPool.Do(token, func(db *sql.DB) (err error) {
+		jr, err = j.Process(rw, req, input, db)
+		return err
+	})
+
+	if err != nil {
+		switch e := err.(type) {
+		case pgx.PgError:
+			var res = struct {
+				Result  string `json:"result"`
+				Code    string `json:"code,omitempty"`
+				Message string `json:"message,omitempty"`
+			}{
+				Result:  "failure",
+				Code:    e.Code,
+				Message: e.Message,
+			}
+			rw.Header().Set("Content-Type", "application/json")
+			rw.WriteHeader(http.StatusInternalServerError)
+			if err := json.NewEncoder(rw).Encode(&res); err != nil {
+				log.Printf("error: %v\n", err)
+			}
+		case JSONError:
+			rw.Header().Set("Content-Type", "application/json")
+			rw.WriteHeader(http.StatusInternalServerError)
+			var res = struct {
+				Message string `json:"message"`
+			}{
+				Message: e.Message,
+			}
+			if err := json.NewEncoder(rw).Encode(&res); err != nil {
+				log.Printf("error: %v\n", err)
+			}
+		default:
+			log.Printf("err: %v\n", err)
+			http.Error(rw,
+				"error: "+err.Error(),
+				http.StatusInternalServerError)
+		}
+		return
+	}
+
+	rw.Header().Set("Content-Type", "application/json")
+	rw.WriteHeader(jr.Code)
+	if err := json.NewEncoder(rw).Encode(jr.Result); err != nil {
+		log.Printf("error: %v\n", err)
+	}
+}
--- a/controllers/routes.go	Thu Jul 26 15:43:19 2018 +0200
+++ b/controllers/routes.go	Thu Jul 26 17:07:03 2018 +0200
@@ -14,8 +14,15 @@
 
 	sysAdmin := auth.EnsureRole("sys_admin")
 
-	api.Handle("/users/{user}", sysAdmin(updateUser)).Methods(http.MethodPut)
-	api.Handle("/users", sysAdmin(createUser)).Methods(http.MethodPost)
+	api.Handle("/users", sysAdmin(&JSONHandler{
+		Input:   func() interface{} { return new(User) },
+		Process: createUser,
+	})).Methods(http.MethodPost)
+
+	api.Handle("/users/{user}", sysAdmin(&JSONHandler{
+		Input:   func() interface{} { return new(User) },
+		Process: updateUser,
+	})).Methods(http.MethodPut)
 
 	api.HandleFunc("/login", login).
 		Methods(http.MethodGet, http.MethodPost)
--- a/controllers/user.go	Thu Jul 26 15:43:19 2018 +0200
+++ b/controllers/user.go	Thu Jul 26 17:07:03 2018 +0200
@@ -4,14 +4,11 @@
 	"database/sql"
 	"encoding/json"
 	"errors"
-	"log"
 	"net/http"
 	"regexp"
 	"strings"
 
-	"gemma.intevation.de/gemma/auth"
 	"github.com/gorilla/mux"
-	"github.com/jackc/pgx"
 )
 
 type (
@@ -120,138 +117,100 @@
 	return errNoValidRole
 }
 
-func updateUser(rw http.ResponseWriter, req *http.Request) {
+func updateUser(
+	rw http.ResponseWriter, req *http.Request,
+	input interface{},
+	db *sql.DB,
+) (jr JSONResult, err error) {
 
 	user := mux.Vars(req)["user"]
 	if user == "" {
-		http.Error(rw, "error: user empty", http.StatusBadRequest)
-		return
-	}
-
-	var newUser User
-
-	defer req.Body.Close()
-	if err := json.NewDecoder(req.Body).Decode(&newUser); err != nil {
-		http.Error(rw, "error: "+err.Error(), http.StatusBadRequest)
+		err = JSONError{http.StatusBadRequest, "error: user empty"}
 		return
 	}
 
-	token, _ := auth.GetToken(req)
-	err := auth.ConnPool.Do(token, func(db *sql.DB) (err error) {
-		if newUser.Extent == nil {
-			_, err = db.Exec(
-				updateUserSQL,
-				user,
-				string(newUser.Role),
-				newUser.User,
-				newUser.Password,
-				string(newUser.Country),
-				string(newUser.Email),
-			)
-		} else {
-			_, err = db.Exec(
-				updateUserExtentSQL,
-				user,
-				string(newUser.Role),
-				newUser.User,
-				newUser.Password,
-				string(newUser.Country),
-				newUser.Extent.X1, newUser.Extent.Y1,
-				newUser.Extent.X2, newUser.Extent.Y2,
-				string(newUser.Email),
-			)
-		}
-		return
-	})
+	newUser := input.(*User)
 
-	var res struct {
-		Result  string `json:"result"`
-		Code    string `json:"code,omitempty"`
-		Message string `json:"message,omitempty"`
+	if newUser.Extent == nil {
+		_, err = db.Exec(
+			updateUserSQL,
+			user,
+			string(newUser.Role),
+			newUser.User,
+			newUser.Password,
+			string(newUser.Country),
+			string(newUser.Email),
+		)
+	} else {
+		_, err = db.Exec(
+			updateUserExtentSQL,
+			user,
+			string(newUser.Role),
+			newUser.User,
+			newUser.Password,
+			string(newUser.Country),
+			newUser.Extent.X1, newUser.Extent.Y1,
+			newUser.Extent.X2, newUser.Extent.Y2,
+			string(newUser.Email),
+		)
 	}
 
 	if err != nil {
-		if pgErr, ok := err.(pgx.PgError); ok {
-			res.Result = "failure"
-			res.Code = pgErr.Code
-			res.Message = pgErr.Message
-		} else {
-			log.Printf("err: %v\n", err)
-			http.Error(rw,
-				"error: "+err.Error(),
-				http.StatusInternalServerError)
-			return
-		}
-	} else {
-		res.Result = "success"
-	}
-
-	rw.Header().Set("Content-Type", "application/json")
-	if err := json.NewEncoder(rw).Encode(&res); err != nil {
-		log.Printf("error: %v\n", err)
-	}
-}
-
-func createUser(rw http.ResponseWriter, req *http.Request) {
-
-	var user User
-
-	defer req.Body.Close()
-	if err := json.NewDecoder(req.Body).Decode(&user); err != nil {
-		http.Error(rw, "error: "+err.Error(), http.StatusBadRequest)
 		return
 	}
 
-	token, _ := auth.GetToken(req)
-	err := auth.ConnPool.Do(token, func(db *sql.DB) (err error) {
-		if user.Extent == nil {
-			_, err = db.Exec(
-				createUserSQL,
-				string(user.Role),
-				user.User,
-				user.Password,
-				string(user.Country),
-				string(user.Email),
-			)
-		} else {
-			_, err = db.Exec(
-				createUserExtentSQL,
-				string(user.Role),
-				user.User,
-				user.Password,
-				string(user.Country),
-				user.Extent.X1, user.Extent.Y1,
-				user.Extent.X2, user.Extent.Y2,
-				string(user.Email),
-			)
-		}
-		return
-	})
+	jr = JSONResult{
+		Code: http.StatusCreated,
+		Result: struct {
+			Result string `json:"result"`
+		}{
+			Result: "success",
+		},
+	}
+	return
+}
+
+func createUser(
+	rw http.ResponseWriter, req *http.Request,
+	input interface{},
+	db *sql.DB,
+) (jr JSONResult, err error) {
+
+	user := input.(*User)
 
-	var res struct {
-		Result  string `json:"result"`
-		Code    string `json:"code,omitempty"`
-		Message string `json:"message,omitempty"`
+	if user.Extent == nil {
+		_, err = db.Exec(
+			createUserSQL,
+			string(user.Role),
+			user.User,
+			user.Password,
+			string(user.Country),
+			string(user.Email),
+		)
+	} else {
+		_, err = db.Exec(
+			createUserExtentSQL,
+			string(user.Role),
+			user.User,
+			user.Password,
+			string(user.Country),
+			user.Extent.X1, user.Extent.Y1,
+			user.Extent.X2, user.Extent.Y2,
+			string(user.Email),
+		)
 	}
 
 	if err != nil {
-		if pgErr, ok := err.(pgx.PgError); ok {
-			res.Result = "failure"
-			res.Code = pgErr.Code
-			res.Message = pgErr.Message
-		} else {
-			log.Printf("err: %v\n", err)
-			http.Error(rw,
-				"error: "+err.Error(),
-				http.StatusInternalServerError)
-			return
-		}
-	} else {
-		res.Result = "success"
+		return
 	}
 
-	rw.Header().Set("Content-Type", "application/json")
-	if err := json.NewEncoder(rw).Encode(&res); err != nil {
-		log.Printf("error: %v\n", err)
+	jr = JSONResult{
+		Code: http.StatusCreated,
+		Result: struct {
+			Result string `json:"result"`
+		}{
+			Result: "success",
+		},
 	}
+	return
 }