internal/content: refactor and export type for content handler funcs

Simplified the content handler and exported the type for use in packages
creating a content server. A content handler now fits the interface of
http.Handler making it more portable. Simplified error handling API.

Change-Id: I2a224b9358d92821af4e0b29c5d77acfb10ef55b
Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/498262
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/godev/internal/content/content.go b/godev/internal/content/content.go
index b522f26..691762f 100644
--- a/godev/internal/content/content.go
+++ b/godev/internal/content/content.go
@@ -50,15 +50,16 @@
 type contentServer struct {
 	fsys     fs.FS
 	fserv    http.Handler
-	handlers map[string]handlerFunc
+	handlers map[string]HandlerFunc
 }
 
-type handler struct {
-	path string
-	fn   handlerFunc
-}
+type HandlerFunc func(http.ResponseWriter, *http.Request) error
 
-type handlerFunc func(http.ResponseWriter, *http.Request, fs.FS) error
+func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	if err := f(w, r); err != nil {
+		handleErr(w, r, err, http.StatusInternalServerError)
+	}
+}
 
 // Server returns a handler that serves HTTP requests with the contents
 // of the file system rooted at fsys. For requests to a path without an
@@ -72,10 +73,11 @@
 // For example, a server can be constructed for a file system with a single
 // template, “index.html“, in a directory, “content“, and a handler:
 //
-//	 s := content.Server(os.DirFS("content"),
-//		 content.Handler("/", func(w http.ReponseWriter, _ *http.Request, fsys fs.FS) error {
-//		 	 return content.Template(w, fsys, "index.html", nil, http.StatusOK)
-//		 }))
+//	  fsys := os.DirFS("content")
+//		 s := content.Server(fsys,
+//			 content.Handler("/", func(w http.ReponseWriter, _ *http.Request) error {
+//			 	 return content.Template(w, fsys, "index.html", nil, http.StatusOK)
+//			 }))
 //
 // or without a handler:
 //
@@ -84,7 +86,7 @@
 // Both examples will render the template index.html for requests to "/".
 func Server(fsys fs.FS, handlers ...*handler) http.Handler {
 	fserv := http.FileServer(http.FS(fsys))
-	hs := make(map[string]handlerFunc)
+	hs := make(map[string]HandlerFunc)
 	for _, h := range handlers {
 		if _, ok := hs[h.path]; ok {
 			panic("multiple registrations for " + h.path)
@@ -94,21 +96,23 @@
 	return &contentServer{fsys, fserv, hs}
 }
 
-func Handler(path string, h handlerFunc) *handler {
+type handler struct {
+	path string
+	fn   HandlerFunc
+}
+
+func Handler(path string, h HandlerFunc) *handler {
 	return &handler{path, h}
 }
 
 func (c *contentServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	if len(r.URL.Path) > 255 {
-		Error(w, r, errors.New("url too long"), http.StatusBadRequest)
+		handleErr(w, r, errors.New("url too long"), http.StatusBadRequest)
 		return
 	}
 
-	if handler, ok := c.handlers[r.URL.Path]; ok {
-		err := handler(w, r, c.fsys)
-		if err != nil {
-			Error(w, r, err, http.StatusInternalServerError)
-		}
+	if h, ok := c.handlers[r.URL.Path]; ok {
+		h.ServeHTTP(w, r)
 		return
 	}
 
@@ -120,7 +124,7 @@
 
 	filepath, info, err := stat(c.fsys, r.URL.Path)
 	if errors.Is(err, fs.ErrNotExist) {
-		Error(w, r, errors.New("page not found"), http.StatusNotFound)
+		handleErr(w, r, errors.New(http.StatusText(http.StatusNotFound)), http.StatusNotFound)
 		return
 	}
 	if err == nil {
@@ -140,7 +144,7 @@
 		}
 	}
 	if err != nil {
-		Error(w, r, err, http.StatusInternalServerError)
+		handleErr(w, r, err, http.StatusInternalServerError)
 	}
 }
 
@@ -205,8 +209,30 @@
 	return nil
 }
 
-// Error writes an error as an HTTP response with a status code.
-func Error(w http.ResponseWriter, req *http.Request, err error, code int) {
+// Error annotates an error with http status information.
+func Error(err error, code int) error {
+
+	return &contentError{err, code}
+}
+
+// Error status returns a content error from a status code.
+func ErrorStatus(code int) error {
+	err := errors.New(http.StatusText(code))
+	return &contentError{err, code}
+}
+
+type contentError struct {
+	err  error
+	Code int
+}
+
+func (e *contentError) Error() string { return e.err.Error() }
+
+// handleErr writes an error as an HTTP response with a status code.
+func handleErr(w http.ResponseWriter, req *http.Request, err error, code int) {
+	if cerr, ok := err.(*contentError); ok {
+		code = cerr.Code
+	}
 	if code == http.StatusInternalServerError {
 		http.Error(w, http.StatusText(http.StatusInternalServerError), code)
 	} else {
diff --git a/godev/internal/content/content_test.go b/godev/internal/content/content_test.go
index 1d2bcc5..3f65036 100644
--- a/godev/internal/content/content_test.go
+++ b/godev/internal/content/content_test.go
@@ -19,11 +19,12 @@
 )
 
 func TestServer_ServeHTTP(t *testing.T) {
-	server := Server(os.DirFS("testdata"),
-		Handler("/data", handleTemplate),
-		Handler("/json", handleJSON),
-		Handler("/text", handleText),
-		Handler("/error", handleError),
+	fsys := os.DirFS("testdata")
+	server := Server(fsys,
+		Handler("/data", handleTemplate(fsys)),
+		Handler("/json", handleJSON()),
+		Handler("/text", handleText()),
+		Handler("/error", handleError()),
 	)
 
 	tests := []struct {
@@ -140,19 +141,23 @@
 	}
 }
 
-func handleTemplate(w http.ResponseWriter, _ *http.Request, fsys fs.FS) error {
-	return Template(w, fsys, "data.html", "Data from Handler", http.StatusOK)
+func handleTemplate(fsys fs.FS) HandlerFunc {
+	return func(w http.ResponseWriter, _ *http.Request) error {
+		return Template(w, fsys, "data.html", "Data from Handler", http.StatusOK)
+	}
 }
-
-func handleJSON(w http.ResponseWriter, _ *http.Request, fsys fs.FS) error {
-	return JSON(w, struct{ Data string }{Data: "Data"}, http.StatusOK)
+func handleJSON() HandlerFunc {
+	return func(w http.ResponseWriter, _ *http.Request) error {
+		return JSON(w, struct{ Data string }{Data: "Data"}, http.StatusOK)
+	}
 }
-
-func handleText(w http.ResponseWriter, _ *http.Request, fsys fs.FS) error {
-	return Text(w, "Hello, World!", http.StatusOK)
+func handleText() HandlerFunc {
+	return func(w http.ResponseWriter, _ *http.Request) error {
+		return Text(w, "Hello, World!", http.StatusOK)
+	}
 }
-
-func handleError(w http.ResponseWriter, r *http.Request, fsys fs.FS) error {
-	Error(w, r, errors.New("Bad Request"), http.StatusBadRequest)
-	return nil
+func handleError() HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) error {
+		return Error(errors.New("Bad Request"), http.StatusBadRequest)
+	}
 }
diff --git a/godev/internal/content/testdata/404.html.out b/godev/internal/content/testdata/404.html.out
index 3261618..e3bf314 100644
--- a/godev/internal/content/testdata/404.html.out
+++ b/godev/internal/content/testdata/404.html.out
@@ -1 +1 @@
-page not found
+Not Found