⚡️ Accept array of string as valid input schema path. (#134)
* ⚡️ Fixes #88. Accept array of string as valid input schema path.
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@ When releasing a new version:
|
||||
### New features:
|
||||
|
||||
- genqlient now generates getter methods for all fields, even those which do not implement a genqlient-generated interface; this can be useful for callers who wish to define their own interface and have several unrelated genqlient types which have the same fields implement it.
|
||||
|
||||
- genqlient config now accepts either a single or multiple schema files for the `schema` field.
|
||||
### Bug fixes:
|
||||
|
||||
- In certain very rare cases involving duplicate fields in fragment spreads, genqlient would generate code that failed to compile due to duplicate methods not getting promoted; genqlient now generates correct types. (See #126 for a more complete description.)
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
# The filename with the GraphQL schema (in SDL format), relative to
|
||||
# genqlient.yaml.
|
||||
# This can also be a list of filenames, such as:
|
||||
# schema:
|
||||
# - user.graphql
|
||||
# - ./schema/*.graphql
|
||||
# - ./another_directory/**/*.graphqls
|
||||
schema: schema.graphql
|
||||
|
||||
# Filenames or globs with the operations for which to generate code, relative
|
||||
|
||||
+76
-3
@@ -6,7 +6,10 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@@ -17,7 +20,7 @@ import (
|
||||
type Config struct {
|
||||
// The following fields are documented at:
|
||||
// https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||
Schema string `yaml:"schema"`
|
||||
Schema StringList `yaml:"schema"`
|
||||
Operations []string `yaml:"operations"`
|
||||
Generated string `yaml:"generated"`
|
||||
Package string `yaml:"package"`
|
||||
@@ -55,8 +58,9 @@ type TypeBinding struct {
|
||||
// typically the directory of the config file.
|
||||
func (c *Config) ValidateAndFillDefaults(baseDir string) error {
|
||||
c.baseDir = baseDir
|
||||
// Make paths relative to config dir
|
||||
c.Schema = filepath.Join(baseDir, c.Schema)
|
||||
for i := range c.Schema {
|
||||
c.Schema[i] = filepath.Join(baseDir, c.Schema[i])
|
||||
}
|
||||
for i := range c.Operations {
|
||||
c.Operations[i] = filepath.Join(baseDir, c.Operations[i])
|
||||
}
|
||||
@@ -122,3 +126,72 @@ func initConfig(filename string) error {
|
||||
_, err = io.Copy(w, r)
|
||||
return errorf(nil, "unable to write default genqlient.yaml: %v", err)
|
||||
}
|
||||
|
||||
var path2regex = strings.NewReplacer(
|
||||
`.`, `\.`,
|
||||
`*`, `.+`,
|
||||
`\`, `[\\/]`,
|
||||
`/`, `[\\/]`,
|
||||
)
|
||||
|
||||
// loadSchemaSources parses the schema file path globs. Parses graphql files,
|
||||
// and returns the parsed ast.Source objects.
|
||||
// Sourced From:
|
||||
// https://github.com/99designs/gqlgen/blob/1a0b19feff6f02d2af6631c9d847bc243f8ede39/codegen/config/config.go#L129-L181
|
||||
func loadSchemaSources(schemas StringList) ([]*ast.Source, error) {
|
||||
preGlobbing := schemas
|
||||
schemas = StringList{}
|
||||
source := make([]*ast.Source, 0)
|
||||
for _, f := range preGlobbing {
|
||||
var matches []string
|
||||
|
||||
// for ** we want to override default globbing patterns and walk all
|
||||
// subdirectories to match schema files.
|
||||
if strings.Contains(f, "**") {
|
||||
pathParts := strings.SplitN(f, "**", 2)
|
||||
rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`)
|
||||
// turn the rest of the glob into a regex, anchored only at the end because ** allows
|
||||
// for any number of dirs in between and walk will let us match against the full path name
|
||||
globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`)
|
||||
|
||||
if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) {
|
||||
matches = append(matches, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, errorf(nil, "failed to walk schema at root %s: %w", pathParts[0], err)
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
matches, err = filepath.Glob(f)
|
||||
if err != nil {
|
||||
return nil, errorf(nil, "failed to glob schema filename %s: %w", f, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range matches {
|
||||
if schemas.Has(m) {
|
||||
continue
|
||||
}
|
||||
schemas = append(schemas, m)
|
||||
}
|
||||
}
|
||||
for _, filename := range schemas {
|
||||
filename = filepath.ToSlash(filename)
|
||||
var err error
|
||||
var schemaRaw []byte
|
||||
schemaRaw, err = ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, errorf(nil, "unable to open schema: %w", err)
|
||||
}
|
||||
|
||||
source = append(source, &ast.Source{Name: filename, Input: string(schemaRaw)})
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestGenerate(t *testing.T) {
|
||||
|
||||
t.Run(sourceFilename, func(t *testing.T) {
|
||||
generated, err := Generate(&Config{
|
||||
Schema: filepath.Join(dataDir, "schema.graphql"),
|
||||
Schema: []string{filepath.Join(dataDir, "schema.graphql")},
|
||||
Operations: []string{filepath.Join(dataDir, sourceFilename)},
|
||||
Package: "test",
|
||||
Generated: goFilename,
|
||||
@@ -197,7 +197,7 @@ func TestGenerateWithConfig(t *testing.T) {
|
||||
baseDir := filepath.Join(dataDir, test.baseDir)
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := config.ValidateAndFillDefaults(baseDir)
|
||||
config.Schema = filepath.Join(dataDir, "schema.graphql")
|
||||
config.Schema = []string{filepath.Join(dataDir, "schema.graphql")}
|
||||
config.Operations = []string{filepath.Join(dataDir, sourceFilename)}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -256,7 +256,7 @@ func TestGenerateErrors(t *testing.T) {
|
||||
|
||||
t.Run(testFilename, func(t *testing.T) {
|
||||
_, err := Generate(&Config{
|
||||
Schema: filepath.Join(errorsDir, schemaFilename),
|
||||
Schema: []string{filepath.Join(errorsDir, schemaFilename)},
|
||||
Operations: []string{filepath.Join(errorsDir, sourceFilename)},
|
||||
Package: "test",
|
||||
Generated: os.DevNull,
|
||||
|
||||
+6
-9
@@ -16,19 +16,16 @@ import (
|
||||
"github.com/vektah/gqlparser/v2/validator"
|
||||
)
|
||||
|
||||
func getSchema(filename string) (*ast.Schema, error) {
|
||||
text, err := ioutil.ReadFile(filename)
|
||||
func getSchema(filePatterns StringList) (*ast.Schema, error) {
|
||||
sources, err := loadSchemaSources(filePatterns)
|
||||
if err != nil {
|
||||
return nil, errorf(nil, "unreadable schema file %v: %v", filename, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schema, graphqlError := gqlparser.LoadSchema(
|
||||
&ast.Source{Name: filename, Input: string(text)})
|
||||
schema, graphqlError := gqlparser.LoadSchema(sources...)
|
||||
if graphqlError != nil {
|
||||
return nil, errorf(nil, "invalid schema file %v: %v",
|
||||
filename, graphqlError)
|
||||
filename, _ := graphqlError.Extensions["file"].(string)
|
||||
return nil, errorf(nil, "invalid schema file %v: %v", filename, graphqlError)
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package generate
|
||||
|
||||
// StringList provides yaml unmarshaler to accept both `string` and `[]string` as a valid type.
|
||||
// Sourced from:
|
||||
// https://github.com/99designs/gqlgen/blob/1a0b19feff6f02d2af6631c9d847bc243f8ede39/codegen/config/config.go#L302-L329
|
||||
type StringList []string
|
||||
|
||||
func (a *StringList) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var single string
|
||||
err := unmarshal(&single)
|
||||
if err == nil {
|
||||
*a = []string{single}
|
||||
return nil
|
||||
}
|
||||
|
||||
var multi []string
|
||||
err = unmarshal(&multi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*a = multi
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a StringList) Has(file string) bool {
|
||||
for _, existing := range a {
|
||||
if existing == file {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -14,7 +14,6 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -43,17 +42,14 @@ github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -64,7 +60,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
|
||||
|
||||
Reference in New Issue
Block a user