diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 33bdfaf8..f1bd00d6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -22,10 +22,12 @@ When releasing a new version: ### 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: - 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. ### Bug fixes: diff --git a/docs/genqlient.yaml b/docs/genqlient.yaml index 3cd37991..1ff58b67 100644 --- a/docs/genqlient.yaml +++ b/docs/genqlient.yaml @@ -8,15 +8,17 @@ # schema: # - user.graphql # - ./schema/*.graphql -# - ./another_directory/**/*.graphqls +# - ./another_directory/*/*.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. # # These may be .graphql files, containing the queries in SDL format, or # Go files, in which case any string-literal starting with (optional # whitespace and) the string "# @genqlient" will be extracted as a query. +# +# Like schema, this may be a single file or a list. operations: - genqlient.graphql - "pkg/*.go" diff --git a/generate/config.go b/generate/config.go index d96cd549..b6a28651 100644 --- a/generate/config.go +++ b/generate/config.go @@ -6,10 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" - "regexp" - "strings" - "github.com/vektah/gqlparser/v2/ast" "gopkg.in/yaml.v2" ) @@ -21,7 +18,7 @@ type Config struct { // The following fields are documented at: // https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml Schema StringList `yaml:"schema"` - Operations []string `yaml:"operations"` + Operations StringList `yaml:"operations"` Generated string `yaml:"generated"` Package string `yaml:"package"` ExportOperations string `yaml:"export_operations"` @@ -126,72 +123,3 @@ 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 -} diff --git a/generate/parse.go b/generate/parse.go index bb6e1efe..b26b7dea 100644 --- a/generate/parse.go +++ b/generate/parse.go @@ -16,20 +16,30 @@ import ( "github.com/vektah/gqlparser/v2/validator" ) -func getSchema(filePatterns StringList) (*ast.Schema, error) { - sources, err := loadSchemaSources(filePatterns) +func getSchema(globs StringList) (*ast.Schema, error) { + filenames, err := expandFilenames(globs) if err != nil { 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...) if graphqlError != nil { - filename, _ := graphqlError.Extensions["file"].(string) - return nil, errorf(nil, "invalid schema file %v: %v", filename, graphqlError) + return nil, errorf(nil, "invalid schema: %v", graphqlError) } + 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) if err != nil { return nil, err @@ -44,7 +54,25 @@ func getAndValidateQueries(basedir string, filenames []string, schema *ast.Schem 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 // 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...) } - expandedFilenames := make([]string, 0, len(filenames)) - for _, filename := range filenames { - matches, err := filepath.Glob(filename) - if err != nil { - return nil, errorf(nil, "can't expand file-glob %v: %v", filename, err) - } - expandedFilenames = append(expandedFilenames, matches...) + filenames, err := expandFilenames(globs) + if err != nil { + return nil, err } - for _, filename := range expandedFilenames { + for _, filename := range filenames { text, err := ioutil.ReadFile(filename) if err != nil { return nil, errorf(nil, "unreadable query-spec file %v: %v", filename, err) diff --git a/generate/stringlist.go b/generate/stringlist.go index c49e76ce..a41f2216 100644 --- a/generate/stringlist.go +++ b/generate/stringlist.go @@ -22,12 +22,3 @@ func (a *StringList) UnmarshalYAML(unmarshal func(interface{}) error) error { *a = multi return nil } - -func (a StringList) Has(file string) bool { - for _, existing := range a { - if existing == file { - return true - } - } - return false -} diff --git a/generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-go b/generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-go index 6aad6cd5..6c9eca41 100644 --- a/generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-go +++ b/generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-go @@ -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 } diff --git a/generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-graphql b/generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-graphql index 6aad6cd5..6c9eca41 100644 --- a/generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-graphql +++ b/generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-graphql @@ -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 } diff --git a/internal/integration/genqlient.yaml b/internal/integration/genqlient.yaml index 0fd9fb6b..50c2cf4e 100644 --- a/internal/integration/genqlient.yaml +++ b/internal/integration/genqlient.yaml @@ -1,6 +1,5 @@ schema: schema.graphql -operations: -- "*_test.go" +operations: "*_test.go" generated: generated.go allow_broken_features: true bindings: