diff --git a/graphql.go b/graphql.go index c878ee3..636d5c6 100644 --- a/graphql.go +++ b/graphql.go @@ -4,6 +4,7 @@ package api import ( "net/http" + "strings" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" @@ -35,7 +36,7 @@ func WithPlayground() GraphQLOption { // The default path is "/graphql". func WithGraphQLPath(path string) GraphQLOption { return func(cfg *graphqlConfig) { - cfg.path = path + cfg.path = normaliseGraphQLPath(path) } } @@ -55,6 +56,22 @@ func mountGraphQL(r *gin.Engine, cfg *graphqlConfig) { } } +// normaliseGraphQLPath coerces custom GraphQL paths into a stable form. +// The path always begins with a single slash and never ends with one. +func normaliseGraphQLPath(path string) string { + path = strings.TrimSpace(path) + if path == "" { + return defaultGraphQLPath + } + + path = "/" + strings.Trim(path, "/") + if path == "/" { + return defaultGraphQLPath + } + + return path +} + // wrapHTTPHandler adapts a standard http.Handler to a Gin handler function. func wrapHTTPHandler(h http.Handler) gin.HandlerFunc { return func(c *gin.Context) { diff --git a/graphql_test.go b/graphql_test.go index e201858..47c6dce 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -192,6 +192,72 @@ func TestWithGraphQL_Good_CustomPath(t *testing.T) { } } +func TestWithGraphQL_Good_NormalisesCustomPath(t *testing.T) { + gin.SetMode(gin.TestMode) + + e, err := api.New(api.WithGraphQL(newTestSchema(), api.WithGraphQLPath(" /gql/ "), api.WithPlayground())) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + srv := httptest.NewServer(e.Handler()) + defer srv.Close() + + body := `{"query":"{ name }"}` + resp, err := http.Post(srv.URL+"/gql", "application/json", strings.NewReader(body)) + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("expected 200 at normalised /gql, got %d", resp.StatusCode) + } + + pgResp, err := http.Get(srv.URL + "/gql/playground") + if err != nil { + t.Fatalf("playground request failed: %v", err) + } + defer pgResp.Body.Close() + + if pgResp.StatusCode != http.StatusOK { + t.Fatalf("expected 200 at normalised /gql/playground, got %d", pgResp.StatusCode) + } +} + +func TestWithGraphQL_Good_DefaultPathWhenEmptyCustomPath(t *testing.T) { + gin.SetMode(gin.TestMode) + + e, err := api.New(api.WithGraphQL(newTestSchema(), api.WithGraphQLPath(""), api.WithPlayground())) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + srv := httptest.NewServer(e.Handler()) + defer srv.Close() + + body := `{"query":"{ name }"}` + resp, err := http.Post(srv.URL+"/graphql", "application/json", strings.NewReader(body)) + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("expected 200 at default /graphql, got %d", resp.StatusCode) + } + + pgResp, err := http.Get(srv.URL + "/graphql/playground") + if err != nil { + t.Fatalf("playground request failed: %v", err) + } + defer pgResp.Body.Close() + + if pgResp.StatusCode != http.StatusOK { + t.Fatalf("expected 200 at default /graphql/playground, got %d", pgResp.StatusCode) + } +} + func TestWithGraphQL_Good_CombinesWithOtherMiddleware(t *testing.T) { gin.SetMode(gin.TestMode)