Align support for multiple operation-files and multiple schema-files (#137)
Multiple schema-files are now supported as of #134, but the support was a bit different from how we did multiple operation-files. Before anyone starts to depend on the ways the syntaxes differ, let's just make them the same. Since it's easy, I also added support for having just a single operations-file. I also realized while writing this that the type-change is technically breaking (if you call from Go), so documented it as such. I think this is unlikely to affect many people. Test plan: make check
This commit is contained in:
+3
-1
@@ -22,10 +22,12 @@ When releasing a new version:
|
|||||||
|
|
||||||
### Breaking changes:
|
### Breaking changes:
|
||||||
|
|
||||||
|
- The `Config` fields `Schema` and `Operations` are now both of type `StringList`. This does not affect configuration via `genqlient.yaml`, only via the Go API.
|
||||||
|
|
||||||
### New features:
|
### 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 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.
|
- genqlient config now accepts either a single or multiple files for the `schema` and `operations` fields (previously it accepted only one `schema`, and required a list of `operations` files).
|
||||||
- The `typename` option can now be used on basic types (string, int, etc) as well as structs; this can be useful to have genqlient define new types like `type Language string` and use that type for specified fields.
|
- The `typename` option can now be used on basic types (string, int, etc) as well as structs; this can be useful to have genqlient define new types like `type Language string` and use that type for specified fields.
|
||||||
|
|
||||||
### Bug fixes:
|
### Bug fixes:
|
||||||
|
|||||||
+4
-2
@@ -8,15 +8,17 @@
|
|||||||
# schema:
|
# schema:
|
||||||
# - user.graphql
|
# - user.graphql
|
||||||
# - ./schema/*.graphql
|
# - ./schema/*.graphql
|
||||||
# - ./another_directory/**/*.graphqls
|
# - ./another_directory/*/*.graphql
|
||||||
schema: schema.graphql
|
schema: schema.graphql
|
||||||
|
|
||||||
# Filenames or globs with the operations for which to generate code, relative
|
# Filename(s) or globs with the operations for which to generate code, relative
|
||||||
# to genqlient.yaml.
|
# to genqlient.yaml.
|
||||||
#
|
#
|
||||||
# These may be .graphql files, containing the queries in SDL format, or
|
# These may be .graphql files, containing the queries in SDL format, or
|
||||||
# Go files, in which case any string-literal starting with (optional
|
# Go files, in which case any string-literal starting with (optional
|
||||||
# whitespace and) the string "# @genqlient" will be extracted as a query.
|
# whitespace and) the string "# @genqlient" will be extracted as a query.
|
||||||
|
#
|
||||||
|
# Like schema, this may be a single file or a list.
|
||||||
operations:
|
operations:
|
||||||
- genqlient.graphql
|
- genqlient.graphql
|
||||||
- "pkg/*.go"
|
- "pkg/*.go"
|
||||||
|
|||||||
+1
-73
@@ -6,10 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vektah/gqlparser/v2/ast"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,7 +18,7 @@ type Config struct {
|
|||||||
// The following fields are documented at:
|
// The following fields are documented at:
|
||||||
// https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
// https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||||
Schema StringList `yaml:"schema"`
|
Schema StringList `yaml:"schema"`
|
||||||
Operations []string `yaml:"operations"`
|
Operations StringList `yaml:"operations"`
|
||||||
Generated string `yaml:"generated"`
|
Generated string `yaml:"generated"`
|
||||||
Package string `yaml:"package"`
|
Package string `yaml:"package"`
|
||||||
ExportOperations string `yaml:"export_operations"`
|
ExportOperations string `yaml:"export_operations"`
|
||||||
@@ -126,72 +123,3 @@ func initConfig(filename string) error {
|
|||||||
_, err = io.Copy(w, r)
|
_, err = io.Copy(w, r)
|
||||||
return errorf(nil, "unable to write default genqlient.yaml: %v", err)
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
+38
-14
@@ -16,20 +16,30 @@ import (
|
|||||||
"github.com/vektah/gqlparser/v2/validator"
|
"github.com/vektah/gqlparser/v2/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getSchema(filePatterns StringList) (*ast.Schema, error) {
|
func getSchema(globs StringList) (*ast.Schema, error) {
|
||||||
sources, err := loadSchemaSources(filePatterns)
|
filenames, err := expandFilenames(globs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sources := make([]*ast.Source, len(filenames))
|
||||||
|
for i, filename := range filenames {
|
||||||
|
text, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorf(nil, "unreadable schema file %v: %v", filename, err)
|
||||||
|
}
|
||||||
|
sources[i] = &ast.Source{Name: filename, Input: string(text)}
|
||||||
|
}
|
||||||
|
|
||||||
schema, graphqlError := gqlparser.LoadSchema(sources...)
|
schema, graphqlError := gqlparser.LoadSchema(sources...)
|
||||||
if graphqlError != nil {
|
if graphqlError != nil {
|
||||||
filename, _ := graphqlError.Extensions["file"].(string)
|
return nil, errorf(nil, "invalid schema: %v", graphqlError)
|
||||||
return nil, errorf(nil, "invalid schema file %v: %v", filename, graphqlError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return schema, nil
|
return schema, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAndValidateQueries(basedir string, filenames []string, schema *ast.Schema) (*ast.QueryDocument, error) {
|
func getAndValidateQueries(basedir string, filenames StringList, schema *ast.Schema) (*ast.QueryDocument, error) {
|
||||||
queryDoc, err := getQueries(basedir, filenames)
|
queryDoc, err := getQueries(basedir, filenames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -44,7 +54,25 @@ func getAndValidateQueries(basedir string, filenames []string, schema *ast.Schem
|
|||||||
return queryDoc, nil
|
return queryDoc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getQueries(basedir string, filenames []string) (*ast.QueryDocument, error) {
|
func expandFilenames(globs []string) ([]string, error) {
|
||||||
|
uniqFilenames := make(map[string]bool, len(globs))
|
||||||
|
for _, glob := range globs {
|
||||||
|
matches, err := filepath.Glob(glob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorf(nil, "can't expand file-glob %v: %v", glob, err)
|
||||||
|
}
|
||||||
|
for _, match := range matches {
|
||||||
|
uniqFilenames[match] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filenames := make([]string, 0, len(uniqFilenames))
|
||||||
|
for filename := range uniqFilenames {
|
||||||
|
filenames = append(filenames, filename)
|
||||||
|
}
|
||||||
|
return filenames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueries(basedir string, globs StringList) (*ast.QueryDocument, error) {
|
||||||
// We merge all the queries into a single query-document, since operations
|
// We merge all the queries into a single query-document, since operations
|
||||||
// in one might reference fragments in another.
|
// in one might reference fragments in another.
|
||||||
//
|
//
|
||||||
@@ -57,16 +85,12 @@ func getQueries(basedir string, filenames []string) (*ast.QueryDocument, error)
|
|||||||
mergedQueryDoc.Fragments = append(mergedQueryDoc.Fragments, queryDoc.Fragments...)
|
mergedQueryDoc.Fragments = append(mergedQueryDoc.Fragments, queryDoc.Fragments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
expandedFilenames := make([]string, 0, len(filenames))
|
filenames, err := expandFilenames(globs)
|
||||||
for _, filename := range filenames {
|
if err != nil {
|
||||||
matches, err := filepath.Glob(filename)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, errorf(nil, "can't expand file-glob %v: %v", filename, err)
|
|
||||||
}
|
|
||||||
expandedFilenames = append(expandedFilenames, matches...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filename := range expandedFilenames {
|
for _, filename := range filenames {
|
||||||
text, err := ioutil.ReadFile(filename)
|
text, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorf(nil, "unreadable query-spec file %v: %v", filename, err)
|
return nil, errorf(nil, "unreadable query-spec file %v: %v", filename, err)
|
||||||
|
|||||||
@@ -22,12 +22,3 @@ func (a *StringList) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
*a = multi
|
*a = multi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a StringList) Has(file string) bool {
|
|
||||||
for _, existing := range a {
|
|
||||||
if existing == file {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
testdata/errors/InvalidSchema.schema.graphql:4: invalid schema file testdata/errors/InvalidSchema.schema.graphql: Expected :, found }
|
testdata/errors/InvalidSchema.schema.graphql:4: invalid schema: Expected :, found }
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
testdata/errors/InvalidSchema.schema.graphql:4: invalid schema file testdata/errors/InvalidSchema.schema.graphql: Expected :, found }
|
testdata/errors/InvalidSchema.schema.graphql:4: invalid schema: Expected :, found }
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
schema: schema.graphql
|
schema: schema.graphql
|
||||||
operations:
|
operations: "*_test.go"
|
||||||
- "*_test.go"
|
|
||||||
generated: generated.go
|
generated: generated.go
|
||||||
allow_broken_features: true
|
allow_broken_features: true
|
||||||
bindings:
|
bindings:
|
||||||
|
|||||||
Reference in New Issue
Block a user