Repository: jensneuse/graphql-go-tools
Pull request #166 (2020-01-17 08:30:36)

feature/use http hanle funcs
This PR is a small modification of the work of @spetrunin
Thanks for the contribution.
Merging should close #165 

@spetrunin please Review and give me feedback if you're ok with the modifications.


Patch:
diff --git a/README.md b/README.md
index d2aae569..9e2efaad 100644
--- a/README.md
+++ b/README.md
@@ -145,11 +145,14 @@ Complex validation grows linearly, not exponentially.
     - Implemented Type Extension merging [#108](https://github.com/jensneuse/graphql-go-tools/pull/108)
 - [Patric Vormstein][patric-vormstein-github]
     - Fixed lexer on windows [#92](https://github.com/jensneuse/graphql-go-tools/pull/92)
+- [Sergey Petrunin][sergey-petrunin-github]
+    - Helped cleaning up the API of the pipeline package [#166](https://github.com/jensneuse/graphql-go-tools/pull/166)
 
 [jens-neuse-github]: https://github.com/jensneuse
 [mantas-vidutis-github]: https://github.com/mvid
 [jonas-bergner-github]: https://github.com/java-jonas
 [patric-vormstein-github]: https://github.com/pvormste
+[sergey-petrunin-github]: https://github.com/spetrunin
 
 ## Contributions
 
diff --git a/pkg/playground/fixtures/handlers.golden b/pkg/playground/fixtures/handlers.golden
new file mode 100644
index 00000000..600ed35e
--- /dev/null
+++ b/pkg/playground/fixtures/handlers.golden
@@ -0,0 +1,22 @@
+(playground.Handlers) (len=5 cap=5) {
+ (playground.HandlerConfig) {
+  Path: (string) (len=11) "/playground",
+  Handler: (http.HandlerFunc) <nil>
+ },
+ (playground.HandlerConfig) {
+  Path: (string) (len=14) "playground.css",
+  Handler: (http.HandlerFunc) <nil>
+ },
+ (playground.HandlerConfig) {
+  Path: (string) (len=13) "playground.js",
+  Handler: (http.HandlerFunc) <nil>
+ },
+ (playground.HandlerConfig) {
+  Path: (string) (len=11) "favicon.png",
+  Handler: (http.HandlerFunc) <nil>
+ },
+ (playground.HandlerConfig) {
+  Path: (string) (len=8) "logo.png",
+  Handler: (http.HandlerFunc) <nil>
+ }
+}
diff --git a/pkg/playground/playground.go b/pkg/playground/playground.go
index e5dde482..a9cbf2e6 100644
--- a/pkg/playground/playground.go
+++ b/pkg/playground/playground.go
@@ -4,29 +4,38 @@
 package playground
 
 import (
-	"github.com/gobuffalo/packr"
 	"html/template"
 	"net/http"
 	"path"
+
+	"github.com/gobuffalo/packr"
 )
 
 const (
 	playgroundTemplate = "playgroundTemplate"
-)
 
-const (
 	contentTypeHeader         = "Content-Type"
 	contentTypeImagePNG       = "image/png"
 	contentTypeTextHTML       = "text/html"
 	contentTypeTextCSS        = "text/css"
 	contentTypeTextJavascript = "text/javascript"
+
+	cssFile     = "playground.css"
+	jsFile      = "playground.js"
+	faviconFile = "favicon.png"
+	logoFile    = "logo.png"
 )
 
+// Config is the configuration Object to instruct ConfigureHandlers on how to setup all the http Handlers for the playground
 type Config struct {
-	URLPrefix                   string
-	PlaygroundURL               string
-	GraphqlEndpoint             string
-	GraphQLSubscriptionEndpoint string
+	// PathPrefix is a prefix you intend to put in front of all handlers
+	PathPrefix string
+	// PlaygroundPath is the Path where the playground website should be hosted
+	PlaygroundPath string
+	// GraphqlEndpointPath is the Path where the http Handler for synchronous (Query,Mutation) GraphQL requests should be hosted
+	GraphqlEndpointPath string
+	// GraphQLSubscriptionEndpointPath is the Path where the http Handler for asynchronous (Subscription) GraphQL requests should be hosted
+	GraphQLSubscriptionEndpointPath string
 }
 
 type playgroundTemplateData struct {
@@ -39,86 +48,129 @@ type playgroundTemplateData struct {
 }
 
 type fileConfig struct {
-	fileName        string
-	fileURL         string
-	fileContentType string
+	name        string
+	url         string
+	contentType string
 }
 
-type HttpServeMux interface {
-	HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
+// HandlerConfig is the configuration Object for playground http Handlers
+type HandlerConfig struct {
+	// Path is where the handler should be hosted
+	Path string
+	// Handler is the http.HandlerFunc that should be hosted on the corresponding Path
+	Handler http.HandlerFunc
 }
 
-func ConfigureHandlers(mux HttpServeMux, config Config) error {
-	box := packr.NewBox("./files")
-	playgroundHTML, err := box.FindString("playground.html")
-	if err != nil {
-		return err
-	}
-	templates, err := template.New(playgroundTemplate).Parse(playgroundHTML)
-	if err != nil {
-		return err
-	}
+// Handlers is an array of HandlerConfig
+// The playground expects that you make all assigned Handlers available on the corresponding Path
+type Handlers []HandlerConfig
+
+func (h *Handlers) add(path string, handler http.HandlerFunc) {
+	*h = append(*h, HandlerConfig{
+		Path:    path,
+		Handler: handler,
+	})
+}
+
+// Playground manages the configuration of all HTTP handlers responsible for serving the GraphQL Playground
+type Playground struct {
+	cfg   Config
+	box   packr.Box
+	files []fileConfig
+	data  playgroundTemplateData
+}
+
+// New creates a Playground for given Config
+func New(config Config) *Playground {
 
-	playgroundURL := path.Join(config.URLPrefix, config.PlaygroundURL)
 	data := playgroundTemplateData{
-		CssURL:                  path.Join(config.URLPrefix, "playground.css"),
-		JsURL:                   path.Join(config.URLPrefix, "playground.js"),
-		FavIconURL:              path.Join(config.URLPrefix, "favicon.png"),
-		LogoURL:                 path.Join(config.URLPrefix, "logo.png"),
-		EndpointURL:             config.GraphqlEndpoint,
-		SubscriptionEndpointURL: config.GraphQLSubscriptionEndpoint,
+		CssURL:                  path.Join(config.PathPrefix, cssFile),
+		JsURL:                   path.Join(config.PathPrefix, jsFile),
+		FavIconURL:              path.Join(config.PathPrefix, faviconFile),
+		LogoURL:                 path.Join(config.PathPrefix, logoFile),
+		EndpointURL:             config.GraphqlEndpointPath,
+		SubscriptionEndpointURL: config.GraphQLSubscriptionEndpointPath,
 	}
 
-	mux.HandleFunc(playgroundURL, func(writer http.ResponseWriter, request *http.Request) {
-		writer.Header().Add(contentTypeHeader, contentTypeTextHTML)
-		err := templates.ExecuteTemplate(writer, playgroundTemplate, data)
-		if err != nil {
-			writer.WriteHeader(http.StatusInternalServerError)
-			_, _ = writer.Write([]byte(err.Error()))
-		}
-	})
-
 	files := []fileConfig{
 		{
-			fileName:        "playground.css",
-			fileURL:         data.CssURL,
-			fileContentType: contentTypeTextCSS,
+			name:        cssFile,
+			url:         data.CssURL,
+			contentType: contentTypeTextCSS,
 		},
 		{
-			fileName:        "playground.js",
-			fileURL:         data.JsURL,
-			fileContentType: contentTypeTextJavascript,
+			name:        jsFile,
+			url:         data.JsURL,
+			contentType: contentTypeTextJavascript,
 		},
 		{
-			fileName:        "favicon.png",
-			fileURL:         data.FavIconURL,
-			fileContentType: contentTypeImagePNG,
+			name:        faviconFile,
+			url:         data.FavIconURL,
+			contentType: contentTypeImagePNG,
 		},
 		{
-			fileName:        "logo.png",
-			fileURL:         data.LogoURL,
-			fileContentType: contentTypeImagePNG,
+			name:        logoFile,
+			url:         data.LogoURL,
+			contentType: contentTypeImagePNG,
 		},
 	}
 
-	for i := 0; i < len(files); i++ {
-		if err := configureFileHandler(mux, box, files[i].fileName, files[i].fileURL, files[i].fileContentType); err != nil {
-			return err
+	return &Playground{
+		cfg:   config,
+		box:   packr.NewBox("./files"),
+		files: files,
+		data:  data,
+	}
+}
+
+// Handlers configures and returns all Handlers for the Playground
+func (p *Playground) Handlers() (handlers Handlers, err error) {
+	handlers = make(Handlers, 0, len(p.files)+1)
+
+	if err = p.configurePlaygroundHandler(&handlers); err != nil {
+		return
+	}
+
+	for _, file := range p.files {
+		if err = p.configureFileHandler(&handlers, file); err != nil {
+			return
 		}
 	}
 
-	return nil
+	return
 }
 
-func configureFileHandler(mux HttpServeMux, box packr.Box, fileName, fileURL, contentType string) error {
+func (p *Playground) configurePlaygroundHandler(handlers *Handlers) (err error) {
+	playgroundHTML, err := p.box.FindString("playground.html")
+	if err != nil {
+		return
+	}
+	templates, err := template.New(playgroundTemplate).Parse(playgroundHTML)
+	if err != nil {
+		return
+	}
+
+	playgroundURL := path.Join(p.cfg.PathPrefix, p.cfg.PlaygroundPath)
+	handlers.add(playgroundURL, func(writer http.ResponseWriter, request *http.Request) {
+		writer.Header().Add(contentTypeHeader, contentTypeTextHTML)
+
+		if err := templates.ExecuteTemplate(writer, playgroundTemplate, p.data); err != nil {
+			writer.WriteHeader(http.StatusInternalServerError)
+			_, _ = writer.Write([]byte(err.Error()))
+		}
+	})
+
+	return nil
+}
 
-	data, err := box.Find(fileName)
+func (p *Playground) configureFileHandler(handlers *Handlers, file fileConfig) error {
+	data, err := p.box.Find(file.name)
 	if err != nil {
 		return err
 	}
 
-	mux.HandleFunc(fileURL, func(writer http.ResponseWriter, request *http.Request) {
-		writer.Header().Add(contentTypeHeader, contentType)
+	handlers.add(file.url, func(writer http.ResponseWriter, request *http.Request) {
+		writer.Header().Add(contentTypeHeader, file.contentType)
 		_, _ = writer.Write(data)
 	})
 


Test patch:
diff --git a/pkg/playground/playground_test.go b/pkg/playground/playground_test.go
new file mode 100644
index 00000000..0e046192
--- /dev/null
+++ b/pkg/playground/playground_test.go
@@ -0,0 +1,44 @@
+package playground
+
+import (
+	"bytes"
+	"io/ioutil"
+	"testing"
+
+	"github.com/davecgh/go-spew/spew"
+	"github.com/jensneuse/diffview"
+	"github.com/sebdah/goldie"
+)
+
+func TestConfigureHandlers(t *testing.T) {
+	config := Config{
+		PathPrefix:                      "",
+		PlaygroundPath:                  "/playground",
+		GraphqlEndpointPath:             "/graphql",
+		GraphQLSubscriptionEndpointPath: "/graphqlws",
+	}
+
+	p := New(config)
+
+	handlers, err := p.Handlers()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for i := range handlers {
+		handlers[i].Handler = nil
+	}
+
+	var out bytes.Buffer
+	spew.Fdump(&out, handlers)
+
+	goldie.Assert(t, "handlers", out.Bytes())
+	if t.Failed() {
+		fixture, err := ioutil.ReadFile("./fixtures/handlers.golden")
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		diffview.NewGoland().DiffViewBytes("handlers", fixture, out.Bytes())
+	}
+}