Skip to content

Commit

Permalink
⚡️ Accept array of string as valid input schema path. (#134)
Browse files Browse the repository at this point in the history
* ⚡️ Fixes #88. Accept array of string as valid input schema path.
  • Loading branch information
Hasibul Hasan committed Oct 4, 2021
1 parent 5e3c4d1 commit 59b6df6
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 21 deletions.
2 changes: 1 addition & 1 deletion docs/CHANGELOG.md
Expand Up @@ -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.)
Expand Down
5 changes: 5 additions & 0 deletions docs/genqlient.yaml
Expand Up @@ -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
Expand Down
79 changes: 76 additions & 3 deletions generate/config.go
Expand Up @@ -6,7 +6,10 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/vektah/gqlparser/v2/ast"
"gopkg.in/yaml.v2"
)

Expand All @@ -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"`
Expand Down Expand Up @@ -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])
}
Expand Down Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions generate/generate_test.go
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 6 additions & 9 deletions generate/parse.go
Expand Up @@ -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
}

Expand Down
33 changes: 33 additions & 0 deletions generate/stringlist.go
@@ -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
}
5 changes: 0 additions & 5 deletions go.sum
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -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=
Expand Down

0 comments on commit 59b6df6

Please sign in to comment.