big overhaul of error-formatting, to get positions more consistently
fixes #2
This commit is contained in:
+13
-13
@@ -40,6 +40,8 @@ import (
|
|||||||
// the directive "a" is ignored, "b" and "c" apply to all relevant nodes in the
|
// the directive "a" is ignored, "b" and "c" apply to all relevant nodes in the
|
||||||
// query, "d" applies to arg2 and arg3, and "e" applies to field1 and field2.
|
// query, "d" applies to arg2 and arg3, and "e" applies to field1 and field2.
|
||||||
type GenqlientDirective struct {
|
type GenqlientDirective struct {
|
||||||
|
pos *ast.Position
|
||||||
|
|
||||||
// If set, this argument will be omitted if it's equal to its Go zero
|
// If set, this argument will be omitted if it's equal to its Go zero
|
||||||
// value. For example, given the following query:
|
// value. For example, given the following query:
|
||||||
// # @genqlient(omitempty: true)
|
// # @genqlient(omitempty: true)
|
||||||
@@ -68,23 +70,25 @@ func setBool(dst **bool, v *ast.Value) error {
|
|||||||
ei, err := v.Value(nil) // no vars allowed
|
ei, err := v.Value(nil) // no vars allowed
|
||||||
// TODO: here and below, put positions on these errors
|
// TODO: here and below, put positions on these errors
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid boolean value %v: %w", v, err)
|
return errorf(v.Position, "invalid boolean value %v: %v", v, err)
|
||||||
}
|
}
|
||||||
if b, ok := ei.(bool); ok {
|
if b, ok := ei.(bool); ok {
|
||||||
*dst = &b
|
*dst = &b
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("expected boolean, got non-boolean value %T(%v)", ei, ei)
|
return errorf(v.Position, "expected boolean, got non-boolean value %T(%v)", ei, ei)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
||||||
if dir.Name != "genqlient" {
|
if dir.Name != "genqlient" {
|
||||||
// Actually we just won't get here; we only get here if the line starts
|
// Actually we just won't get here; we only get here if the line starts
|
||||||
// with "# @genqlient", unless there's some sort of bug.
|
// with "# @genqlient", unless there's some sort of bug.
|
||||||
return nil, fmt.Errorf("the only valid comment-directive is @genqlient, got %v", dir.Name)
|
return nil, errorf(dir.Position, "the only valid comment-directive is @genqlient, got %v", dir.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
var retval GenqlientDirective
|
var retval GenqlientDirective
|
||||||
|
retval.pos = dir.Position
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for _, arg := range dir.Arguments {
|
for _, arg := range dir.Arguments {
|
||||||
switch arg.Name {
|
switch arg.Name {
|
||||||
@@ -94,7 +98,7 @@ func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
|||||||
case "pointer":
|
case "pointer":
|
||||||
err = setBool(&retval.Pointer, arg.Value)
|
err = setBool(&retval.Pointer, arg.Value)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown argument %v for @genqlient", arg.Name)
|
return nil, errorf(arg.Position, "unknown argument %v for @genqlient", arg.Name)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -111,16 +115,16 @@ func (dir *GenqlientDirective) validate(node interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
case *ast.VariableDefinition:
|
case *ast.VariableDefinition:
|
||||||
if dir.Omitempty != nil && node.Type.NonNull {
|
if dir.Omitempty != nil && node.Type.NonNull {
|
||||||
return fmt.Errorf("omitempty may only be used on optional arguments")
|
return errorf(dir.pos, "omitempty may only be used on optional arguments")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case *ast.Field:
|
case *ast.Field:
|
||||||
if dir.Omitempty != nil {
|
if dir.Omitempty != nil {
|
||||||
return fmt.Errorf("omitempty is not appilcable to fields")
|
return errorf(dir.pos, "omitempty is not appilcable to fields")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid directive location: %T", node)
|
return errorf(dir.pos, "invalid directive location: %T", node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,13 +178,9 @@ func (g *generator) parsePrecedingComment(
|
|||||||
func parseDirective(line string, pos *ast.Position) (*ast.Directive, error) {
|
func parseDirective(line string, pos *ast.Position) (*ast.Directive, error) {
|
||||||
// HACK: parse the "directive" by making a fake query containing it.
|
// HACK: parse the "directive" by making a fake query containing it.
|
||||||
fakeQuery := fmt.Sprintf("query %v { field }", line)
|
fakeQuery := fmt.Sprintf("query %v { field }", line)
|
||||||
doc, err := parser.ParseQuery(&ast.Source{
|
doc, err := parser.ParseQuery(&ast.Source{Input: fakeQuery})
|
||||||
Name: fmt.Sprintf("@genqlient directive at %v:%v:%v",
|
|
||||||
pos.Src.Name, pos.Line, pos.Column),
|
|
||||||
Input: fakeQuery,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errorf(pos, "invalid genqlient directive: %v", err)
|
||||||
}
|
}
|
||||||
return doc.Operations[0].Directives[0], nil
|
return doc.Operations[0].Directives[0], nil
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-6
@@ -1,7 +1,6 @@
|
|||||||
package generate
|
package generate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go/token"
|
"go/token"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -103,12 +102,12 @@ func (c *Config) ValidateAndFillDefaults(configFilename string) error {
|
|||||||
if c.Package == "" {
|
if c.Package == "" {
|
||||||
abs, err := filepath.Abs(c.Generated)
|
abs, err := filepath.Abs(c.Generated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to guess package-name: %v", err)
|
return errorf(nil, "unable to guess package-name: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
base := filepath.Base(filepath.Dir(abs))
|
base := filepath.Base(filepath.Dir(abs))
|
||||||
if !token.IsIdentifier(base) {
|
if !token.IsIdentifier(base) {
|
||||||
return fmt.Errorf("unable to guess package-name: %v is not a valid identifier", base)
|
return errorf(nil, "unable to guess package-name: %v is not a valid identifier", base)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Package = base
|
c.Package = base
|
||||||
@@ -122,18 +121,18 @@ func ReadAndValidateConfig(filename string) (*Config, error) {
|
|||||||
if filename != "" {
|
if filename != "" {
|
||||||
text, err := ioutil.ReadFile(filename)
|
text, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unreadable config file %v: %v", filename, err)
|
return nil, errorf(nil, "unreadable config file %v: %v", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = yaml.Unmarshal(text, &config)
|
err = yaml.Unmarshal(text, &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid config file %v: %v", filename, err)
|
return nil, errorf(nil, "invalid config file %v: %v", filename, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := config.ValidateAndFillDefaults(filename)
|
err := config.ValidateAndFillDefaults(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid config file %v: %v", filename, err)
|
return nil, errorf(nil, "invalid config file %v: %v", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &config, nil
|
return &config, nil
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vektah/gqlparser/v2/ast"
|
||||||
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorPos struct {
|
||||||
|
filename string
|
||||||
|
line, col int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pos *errorPos) String() string {
|
||||||
|
filename, lineOffset := splitFilename(pos.filename)
|
||||||
|
line := lineOffset + pos.line
|
||||||
|
if line != 0 {
|
||||||
|
return fmt.Sprintf("%v:%v", filename, line)
|
||||||
|
} else {
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type genqlientError struct {
|
||||||
|
pos *errorPos
|
||||||
|
msg string
|
||||||
|
wrapped error
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitFilename(filename string) (name string, lineOffset int) {
|
||||||
|
split := strings.Split(filename, ":")
|
||||||
|
if len(split) != 2 {
|
||||||
|
return filename, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, err := strconv.Atoi(split[1])
|
||||||
|
if err != nil {
|
||||||
|
return split[0], 0
|
||||||
|
}
|
||||||
|
return split[0], offset - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *genqlientError) Error() string {
|
||||||
|
if err.pos != nil {
|
||||||
|
return err.pos.String() + ": " + err.msg
|
||||||
|
} else {
|
||||||
|
return err.msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *genqlientError) Unwrap() error {
|
||||||
|
return err.wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorf(pos *ast.Position, msg string, args ...interface{}) error {
|
||||||
|
// TODO: alternately accept a filename only, or maybe even a go-parser pos
|
||||||
|
|
||||||
|
// We do all our own wrapping, because if the wrapped error already has a
|
||||||
|
// pos, we want to extract it out and put it at the front, not in the
|
||||||
|
// middle.
|
||||||
|
|
||||||
|
var wrapped error
|
||||||
|
var wrapIndex int
|
||||||
|
for i, arg := range args {
|
||||||
|
if wrapped == nil {
|
||||||
|
var ok bool
|
||||||
|
wrapped, ok = arg.(error)
|
||||||
|
if ok {
|
||||||
|
wrapIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrappedGenqlient *genqlientError
|
||||||
|
isGenqlient := errors.As(wrapped, &wrappedGenqlient)
|
||||||
|
var wrappedGraphQL *gqlerror.Error
|
||||||
|
isGraphQL := errors.As(wrapped, &wrappedGraphQL)
|
||||||
|
if !isGraphQL {
|
||||||
|
var wrappedGraphQLList gqlerror.List
|
||||||
|
isGraphQLList := errors.As(wrapped, &wrappedGraphQLList)
|
||||||
|
if isGraphQLList && len(wrappedGraphQLList) > 0 {
|
||||||
|
isGraphQL = true
|
||||||
|
wrappedGraphQL = wrappedGraphQLList[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errPos *errorPos
|
||||||
|
if pos != nil {
|
||||||
|
errPos = &errorPos{
|
||||||
|
filename: pos.Src.Name,
|
||||||
|
line: pos.Line,
|
||||||
|
col: pos.Column,
|
||||||
|
}
|
||||||
|
} else if isGenqlient {
|
||||||
|
errPos = wrappedGenqlient.pos
|
||||||
|
} else if isGraphQL {
|
||||||
|
filename, _ := wrappedGraphQL.Extensions["file"].(string)
|
||||||
|
if filename != "" {
|
||||||
|
var loc gqlerror.Location
|
||||||
|
if len(wrappedGraphQL.Locations) > 0 {
|
||||||
|
loc = wrappedGraphQL.Locations[0]
|
||||||
|
}
|
||||||
|
errPos = &errorPos{
|
||||||
|
filename: filename,
|
||||||
|
line: loc.Line,
|
||||||
|
col: loc.Column,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if wrapped != nil {
|
||||||
|
errText := wrapped.Error()
|
||||||
|
if isGenqlient {
|
||||||
|
errText = wrappedGenqlient.msg
|
||||||
|
} else if isGraphQL {
|
||||||
|
errText = wrappedGraphQL.Message
|
||||||
|
}
|
||||||
|
args[wrapIndex] = errText
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = fmt.Sprintf(msg, args...)
|
||||||
|
|
||||||
|
return &genqlientError{
|
||||||
|
msg: msg,
|
||||||
|
pos: errPos,
|
||||||
|
wrapped: wrapped,
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-8
@@ -3,7 +3,6 @@ package generate
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"go/format"
|
"go/format"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -134,7 +133,7 @@ func (g *generator) getArgument(
|
|||||||
|
|
||||||
func (g *generator) addOperation(op *ast.OperationDefinition) error {
|
func (g *generator) addOperation(op *ast.OperationDefinition) error {
|
||||||
if op.Name == "" {
|
if op.Name == "" {
|
||||||
return fmt.Errorf("operations must have operation-names")
|
return errorf(op.Position, "operations must have operation-names")
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
@@ -199,11 +198,14 @@ func Generate(config *Config) (map[string][]byte, error) {
|
|||||||
// package-name, if it turns out to be more convenient that way. (As-is,
|
// package-name, if it turns out to be more convenient that way. (As-is,
|
||||||
// we generate a broken file, with just (unused) imports.)
|
// we generate a broken file, with just (unused) imports.)
|
||||||
if len(document.Operations) == 0 {
|
if len(document.Operations) == 0 {
|
||||||
return nil, fmt.Errorf("no queries found in %v", config.Operations)
|
// Hard to have a position when there are no operations :(
|
||||||
|
return nil, errorf(nil, "no queries found, looked in: %v",
|
||||||
|
strings.Join(config.Operations, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(document.Fragments) > 0 && !allowBrokenFeatures {
|
if len(document.Fragments) > 0 && !allowBrokenFeatures {
|
||||||
return nil, fmt.Errorf("genqlient does not yet support fragments")
|
return nil, errorf(document.Fragments[0].Position,
|
||||||
|
"genqlient does not yet support fragments")
|
||||||
}
|
}
|
||||||
|
|
||||||
g := newGenerator(config, schema)
|
g := newGenerator(config, schema)
|
||||||
@@ -216,18 +218,18 @@ func Generate(config *Config) (map[string][]byte, error) {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = g.execute("operation.go.tmpl", &buf, g)
|
err = g.execute("operation.go.tmpl", &buf, g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not render template: %v", err)
|
return nil, errorf(nil, "could not render template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
unformatted := buf.Bytes()
|
unformatted := buf.Bytes()
|
||||||
formatted, err := format.Source(unformatted)
|
formatted, err := format.Source(unformatted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not gofmt code: %v\n---unformatted code---\n%v",
|
return nil, errorf(nil, "could not gofmt code: %v\n---unformatted code---\n%v",
|
||||||
err, string(unformatted))
|
err, string(unformatted))
|
||||||
}
|
}
|
||||||
importsed, err := imports.Process(config.Generated, formatted, nil)
|
importsed, err := imports.Process(config.Generated, formatted, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not goimports code: %v\n---unimportsed code---\n%v",
|
return nil, errorf(nil, "could not goimports code: %v\n---unimportsed code---\n%v",
|
||||||
err, string(formatted))
|
err, string(formatted))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +245,7 @@ func Generate(config *Config) (map[string][]byte, error) {
|
|||||||
retval[config.ExportOperations], err = json.MarshalIndent(
|
retval[config.ExportOperations], err = json.MarshalIndent(
|
||||||
exportedOperations{Operations: g.Operations}, "", " ")
|
exportedOperations{Operations: g.Operations}, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to export queries: %v", err)
|
return nil, errorf(nil, "unable to export queries: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package generate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"go/format"
|
"go/format"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -20,7 +19,7 @@ func gofmt(filename, src string) (string, error) {
|
|||||||
src = strings.TrimSpace(src)
|
src = strings.TrimSpace(src)
|
||||||
formatted, err := format.Source([]byte(src))
|
formatted, err := format.Source([]byte(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return src, fmt.Errorf("go parse error in %v: %w", filename, err)
|
return src, errorf(nil, "go parse error in %v: %v", filename, err)
|
||||||
}
|
}
|
||||||
return string(formatted), nil
|
return string(formatted), nil
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,6 @@
|
|||||||
package generate
|
package generate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go/types"
|
"go/types"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -45,7 +44,8 @@ func (g *generator) getRef(fullyQualifiedName string, addImport bool) (qualified
|
|||||||
// confusing, why would you want it. But the empty interface,
|
// confusing, why would you want it. But the empty interface,
|
||||||
// specifically, is useful.
|
// specifically, is useful.
|
||||||
if fullyQualifiedName != "interface{}" && types.Universe.Lookup(fullyQualifiedName) == nil {
|
if fullyQualifiedName != "interface{}" && types.Universe.Lookup(fullyQualifiedName) == nil {
|
||||||
return "", fmt.Errorf(
|
// TODO: pass in pos here
|
||||||
|
return "", errorf(nil,
|
||||||
`unknown name "%v"; expected a builtin or path/to/package.Name`, fullyQualifiedName)
|
`unknown name "%v"; expected a builtin or path/to/package.Name`, fullyQualifiedName)
|
||||||
}
|
}
|
||||||
return fullyQualifiedName, nil
|
return fullyQualifiedName, nil
|
||||||
@@ -60,7 +60,7 @@ func (g *generator) getRef(fullyQualifiedName string, addImport bool) (qualified
|
|||||||
var ok bool
|
var ok bool
|
||||||
alias, ok = g.imports[pkgPath]
|
alias, ok = g.imports[pkgPath]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf(`no alias defined for package "%v"`, pkgPath)
|
return "", errorf(nil, `no alias defined for package "%v"`, pkgPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return alias + "." + localName, nil
|
return alias + "." + localName, nil
|
||||||
|
|||||||
+8
-3
@@ -5,6 +5,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readConfigGenerateAndWrite(configFilename string) error {
|
func readConfigGenerateAndWrite(configFilename string) error {
|
||||||
@@ -21,14 +22,14 @@ func readConfigGenerateAndWrite(configFilename string) error {
|
|||||||
for filename, content := range generated {
|
for filename, content := range generated {
|
||||||
err = os.MkdirAll(filepath.Dir(filename), 0o755)
|
err = os.MkdirAll(filepath.Dir(filename), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return errorf(nil,
|
||||||
"could not create parent directory for generated file %v: %v",
|
"could not create parent directory for generated file %v: %v",
|
||||||
filename, err)
|
filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, content, 0o644)
|
err = ioutil.WriteFile(filename, content, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not write generated file %v: %v",
|
return errorf(nil, "could not write generated file %v: %v",
|
||||||
filename, err)
|
filename, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +51,10 @@ func Main() {
|
|||||||
case 1:
|
case 1:
|
||||||
err = readConfigGenerateAndWrite("")
|
err = readConfigGenerateAndWrite("")
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("usage: %s [config]", os.Args[0])
|
argv0 := os.Args[0]
|
||||||
|
if strings.Contains(argv0, string(filepath.Separator)+"go-build") {
|
||||||
|
argv0 = "go run github.com/Khan/genqlient"
|
||||||
|
}
|
||||||
|
err = errorf(nil, "usage: %s [config]", argv0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-10
@@ -20,13 +20,13 @@ import (
|
|||||||
func getSchema(filename string) (*ast.Schema, error) {
|
func getSchema(filename string) (*ast.Schema, error) {
|
||||||
text, err := ioutil.ReadFile(filename)
|
text, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unreadable schema file %v: %v", filename, err)
|
return nil, errorf(nil, "unreadable schema file %v: %v", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
schema, graphqlError := gqlparser.LoadSchema(
|
schema, graphqlError := gqlparser.LoadSchema(
|
||||||
&ast.Source{Name: filename, Input: string(text)})
|
&ast.Source{Name: filename, Input: string(text)})
|
||||||
if graphqlError != nil {
|
if graphqlError != nil {
|
||||||
return nil, fmt.Errorf("invalid schema file %v: %v",
|
return nil, errorf(nil, "invalid schema file %v: %v",
|
||||||
filename, graphqlError)
|
filename, graphqlError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ func getAndValidateQueries(basedir string, filenames []string, schema *ast.Schem
|
|||||||
// Cf. gqlparser.LoadQuery
|
// Cf. gqlparser.LoadQuery
|
||||||
graphqlErrors := validator.Validate(schema, queryDoc)
|
graphqlErrors := validator.Validate(schema, queryDoc)
|
||||||
if graphqlErrors != nil {
|
if graphqlErrors != nil {
|
||||||
return nil, fmt.Errorf("query-spec does not match schema: %v", graphqlErrors)
|
return nil, errorf(nil, "query-spec does not match schema: %v", graphqlErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryDoc, nil
|
return queryDoc, nil
|
||||||
@@ -64,7 +64,7 @@ func getQueries(basedir string, filenames []string) (*ast.QueryDocument, error)
|
|||||||
for _, filename := range filenames {
|
for _, filename := range filenames {
|
||||||
matches, err := filepath.Glob(filename)
|
matches, err := filepath.Glob(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't expand file-glob %v: %v", filename, err)
|
return nil, errorf(nil, "can't expand file-glob %v: %v", filename, err)
|
||||||
}
|
}
|
||||||
expandedFilenames = append(expandedFilenames, matches...)
|
expandedFilenames = append(expandedFilenames, matches...)
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ func getQueries(basedir string, filenames []string) (*ast.QueryDocument, error)
|
|||||||
for _, filename := range expandedFilenames {
|
for _, filename := range expandedFilenames {
|
||||||
text, err := ioutil.ReadFile(filename)
|
text, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unreadable query-spec file %v: %v", filename, err)
|
return nil, errorf(nil, "unreadable query-spec file %v: %v", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch filepath.Ext(filename) {
|
switch filepath.Ext(filename) {
|
||||||
@@ -95,7 +95,7 @@ func getQueries(basedir string, filenames []string) (*ast.QueryDocument, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown file type: %v", filename)
|
return nil, errorf(nil, "unknown file type: %v", filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ func getQueriesFromString(text string, basedir, filename string) (*ast.QueryDocu
|
|||||||
document, graphqlError := parser.ParseQuery(
|
document, graphqlError := parser.ParseQuery(
|
||||||
&ast.Source{Name: filename, Input: text})
|
&ast.Source{Name: filename, Input: text})
|
||||||
if graphqlError != nil { // ParseQuery returns type *graphql.Error, yuck
|
if graphqlError != nil { // ParseQuery returns type *graphql.Error, yuck
|
||||||
return nil, fmt.Errorf("invalid query-spec file %v: %v", filename, graphqlError)
|
return nil, errorf(nil, "invalid query-spec file %v: %v", filename, graphqlError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return document, nil
|
return document, nil
|
||||||
@@ -123,7 +123,7 @@ func getQueriesFromGo(text string, basedir, filename string) ([]*ast.QueryDocume
|
|||||||
fset := goToken.NewFileSet()
|
fset := goToken.NewFileSet()
|
||||||
f, err := goParser.ParseFile(fset, filename, text, 0)
|
f, err := goParser.ParseFile(fset, filename, text, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid Go file %v: %v", filename, err)
|
return nil, errorf(nil, "invalid Go file %v: %v", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var retval []*ast.QueryDocument
|
var retval []*ast.QueryDocument
|
||||||
@@ -147,9 +147,13 @@ func getQueriesFromGo(text string, basedir, filename string) ([]*ast.QueryDocume
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := fset.Position(basicLit.Pos()).Filename
|
// We put the filename as <real filename>:<line>, which errors.go knows
|
||||||
|
// how to parse back out (since it's what gqlparser will give to us in
|
||||||
|
// our errors).
|
||||||
|
pos := fset.Position(basicLit.Pos())
|
||||||
|
fakeFilename := fmt.Sprintf("%v:%v", pos.Filename, pos.Line)
|
||||||
var query *ast.QueryDocument
|
var query *ast.QueryDocument
|
||||||
query, err = getQueriesFromString(value, basedir, filename)
|
query, err = getQueriesFromString(value, basedir, fakeFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package generate
|
package generate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -26,13 +25,13 @@ func (g *generator) execute(tmplRelFilename string, w io.Writer, data interface{
|
|||||||
var err error
|
var err error
|
||||||
tmpl, err = template.New(tmplRelFilename).Funcs(funcMap).ParseFiles(absFilename)
|
tmpl, err = template.New(tmplRelFilename).Funcs(funcMap).ParseFiles(absFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not load template %v: %v", absFilename, err)
|
return errorf(nil, "could not load template %v: %v", absFilename, err)
|
||||||
}
|
}
|
||||||
g.templateCache[tmplRelFilename] = tmpl
|
g.templateCache[tmplRelFilename] = tmpl
|
||||||
}
|
}
|
||||||
err := tmpl.Execute(w, data)
|
err := tmpl.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not render template: %v", err)
|
return errorf(nil, "could not render template: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
query-spec does not match schema: testdata/errors/InvalidQuery.go:2: Cannot query field "g" on type "Query". Did you mean "f"?
|
testdata/errors/InvalidQuery.go:4: query-spec does not match schema: Cannot query field "g" on type "Query". Did you mean "f"?
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
query-spec does not match schema: testdata/errors/InvalidQuery.graphql:1: Cannot query field "g" on type "Query". Did you mean "f"?
|
testdata/errors/InvalidQuery.graphql:1: query-spec does not match schema: Cannot query field "g" on type "Query". Did you mean "f"?
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
invalid schema file testdata/errors/InvalidSchema.schema.graphql: testdata/errors/InvalidSchema.schema.graphql:4: Expected :, found }
|
testdata/errors/InvalidSchema.schema.graphql:4: invalid schema file testdata/errors/InvalidSchema.schema.graphql: Expected :, found }
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
invalid schema file testdata/errors/InvalidSchema.schema.graphql: testdata/errors/InvalidSchema.schema.graphql:4: Expected :, found }
|
testdata/errors/InvalidSchema.schema.graphql:4: invalid schema file testdata/errors/InvalidSchema.schema.graphql: Expected :, found }
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
no queries found in [testdata/errors/NoQuery.go]
|
no queries found, looked in: testdata/errors/NoQuery.go
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
no queries found in [testdata/errors/NoQuery.graphql]
|
no queries found, looked in: testdata/errors/NoQuery.graphql
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
unknown scalar UnknownScalar: please add it to genqlient.yaml
|
testdata/errors/UnknownScalar.schema.graphql:3: unknown scalar UnknownScalar: please add it to genqlient.yaml
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
unknown scalar UnknownScalar: please add it to genqlient.yaml
|
testdata/errors/UnknownScalar.schema.graphql:3: unknown scalar UnknownScalar: please add it to genqlient.yaml
|
||||||
+37
-18
@@ -22,11 +22,11 @@ func (g *generator) baseTypeForOperation(operation ast.Operation) (*ast.Definiti
|
|||||||
return g.schema.Mutation, nil
|
return g.schema.Mutation, nil
|
||||||
case ast.Subscription:
|
case ast.Subscription:
|
||||||
if !allowBrokenFeatures {
|
if !allowBrokenFeatures {
|
||||||
return nil, fmt.Errorf("genqlient does not yet support subscriptions")
|
return nil, errorf(nil, "genqlient does not yet support subscriptions")
|
||||||
}
|
}
|
||||||
return g.schema.Subscription, nil
|
return g.schema.Subscription, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected operation: %v", operation)
|
return nil, errorf(nil, "unexpected operation: %v", operation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ func (g *generator) getTypeForOperation(operation *ast.OperationDefinition, quer
|
|||||||
|
|
||||||
if def, ok := g.typeMap[name]; ok {
|
if def, ok := g.typeMap[name]; ok {
|
||||||
// TODO: check for and handle conflicts a better way
|
// TODO: check for and handle conflicts a better way
|
||||||
return "", fmt.Errorf("%s defined twice:\n%s", name, def)
|
return "", errorf(operation.Position, "%s defined twice:\n%s", name, def)
|
||||||
}
|
}
|
||||||
|
|
||||||
fields, err := selections(g, operation.SelectionSet, queryOptions)
|
fields, err := selections(g, operation.SelectionSet, queryOptions)
|
||||||
@@ -46,10 +46,10 @@ func (g *generator) getTypeForOperation(operation *ast.OperationDefinition, quer
|
|||||||
|
|
||||||
baseType, err := g.baseTypeForOperation(operation.Operation)
|
baseType, err := g.baseTypeForOperation(operation.Operation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", errorf(operation.Position, "%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.addTypeForDefinition(operation.Name, name, baseType, fields, queryOptions)
|
return g.addTypeForDefinition(operation.Name, name, baseType, operation.Position, fields, queryOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
var builtinTypes = map[string]string{
|
var builtinTypes = map[string]string{
|
||||||
@@ -61,7 +61,13 @@ var builtinTypes = map[string]string{
|
|||||||
"ID": "string",
|
"ID": "string",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *generator) addTypeForDefinition(namePrefix, nameOverride string, typ *ast.Definition, fields []field, options *GenqlientDirective) (name string, err error) {
|
func (g *generator) addTypeForDefinition(
|
||||||
|
namePrefix, nameOverride string,
|
||||||
|
typ *ast.Definition,
|
||||||
|
pos *ast.Position,
|
||||||
|
fields []field,
|
||||||
|
options *GenqlientDirective,
|
||||||
|
) (name string, err error) {
|
||||||
// If this is a builtin type or custom scalar, just refer to it.
|
// If this is a builtin type or custom scalar, just refer to it.
|
||||||
goName, ok := g.Config.Scalars[typ.Name]
|
goName, ok := g.Config.Scalars[typ.Name]
|
||||||
if ok {
|
if ok {
|
||||||
@@ -114,7 +120,7 @@ func (g *generator) addTypeForDefinition(namePrefix, nameOverride string, typ *a
|
|||||||
// name.
|
// name.
|
||||||
builder := &typeBuilder{typeName: name, typeNamePrefix: namePrefix, generator: g}
|
builder := &typeBuilder{typeName: name, typeNamePrefix: namePrefix, generator: g}
|
||||||
fmt.Fprintf(builder, "type %s ", name)
|
fmt.Fprintf(builder, "type %s ", name)
|
||||||
err = builder.writeTypedef(typ, fields, options)
|
err = builder.writeTypedef(typ, pos, fields, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -146,6 +152,7 @@ type field interface {
|
|||||||
Alias() string
|
Alias() string
|
||||||
Options() (*GenqlientDirective, error)
|
Options() (*GenqlientDirective, error)
|
||||||
Type() *ast.Type
|
Type() *ast.Type
|
||||||
|
Pos() *ast.Position
|
||||||
SubFields() ([]field, error)
|
SubFields() ([]field, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +183,10 @@ func (s outputField) Type() *ast.Type {
|
|||||||
return s.field.Definition.Type
|
return s.field.Definition.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s outputField) Pos() *ast.Position {
|
||||||
|
return s.field.Position
|
||||||
|
}
|
||||||
|
|
||||||
func (s outputField) SubFields() ([]field, error) {
|
func (s outputField) SubFields() ([]field, error) {
|
||||||
return selections(s.generator, s.field.SelectionSet, s.queryOptions)
|
return selections(s.generator, s.field.SelectionSet, s.queryOptions)
|
||||||
}
|
}
|
||||||
@@ -186,10 +197,12 @@ func selections(g *generator, selectionSet ast.SelectionSet, options *GenqlientD
|
|||||||
switch selection := selection.(type) {
|
switch selection := selection.(type) {
|
||||||
case *ast.Field:
|
case *ast.Field:
|
||||||
retval[i] = outputField{g, options, selection}
|
retval[i] = outputField{g, options, selection}
|
||||||
case *ast.FragmentSpread, *ast.InlineFragment:
|
case *ast.FragmentSpread:
|
||||||
return nil, fmt.Errorf("not implemented: %T", selection)
|
return nil, errorf(selection.Position, "not implemented: %T", selection)
|
||||||
|
case *ast.InlineFragment:
|
||||||
|
return nil, errorf(selection.Position, "not implemented: %T", selection)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid selection type: %v", selection)
|
return nil, errorf(nil, "invalid selection type: %T", selection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return retval, nil
|
return retval, nil
|
||||||
@@ -209,7 +222,8 @@ func (s inputField) Options() (*GenqlientDirective, error) {
|
|||||||
}
|
}
|
||||||
return s.queryOptions.merge(directive), nil
|
return s.queryOptions.merge(directive), nil
|
||||||
}
|
}
|
||||||
func (s inputField) Type() *ast.Type { return s.field.Type }
|
func (s inputField) Type() *ast.Type { return s.field.Type }
|
||||||
|
func (s inputField) Pos() *ast.Position { return s.field.Position }
|
||||||
|
|
||||||
func (s inputField) SubFields() ([]field, error) {
|
func (s inputField) SubFields() ([]field, error) {
|
||||||
return selectionsForType(s.generator, s.field.Type, s.queryOptions), nil
|
return selectionsForType(s.generator, s.field.Type, s.queryOptions), nil
|
||||||
@@ -236,7 +250,7 @@ func (builder *typeBuilder) writeField(field field) error {
|
|||||||
if typ == nil {
|
if typ == nil {
|
||||||
// Unclear why gqlparser hasn't already rejected this,
|
// Unclear why gqlparser hasn't already rejected this,
|
||||||
// but empirically it might not.
|
// but empirically it might not.
|
||||||
return fmt.Errorf("undefined field %v", field.Alias())
|
return errorf(field.Pos(), "undefined field %v", field.Alias())
|
||||||
}
|
}
|
||||||
|
|
||||||
fields, err := field.SubFields()
|
fields, err := field.SubFields()
|
||||||
@@ -291,7 +305,7 @@ func (builder *typeBuilder) writeType(namePrefix, nameOverride string, typ *ast.
|
|||||||
|
|
||||||
def := builder.schema.Types[typ.Name()]
|
def := builder.schema.Types[typ.Name()]
|
||||||
// Writes a typedef elsewhere (if not already defined)
|
// Writes a typedef elsewhere (if not already defined)
|
||||||
name, err := builder.addTypeForDefinition(namePrefix, nameOverride, def, fields, options)
|
name, err := builder.addTypeForDefinition(namePrefix, nameOverride, def, typ.Position, fields, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -300,7 +314,12 @@ func (builder *typeBuilder) writeType(namePrefix, nameOverride string, typ *ast.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field, options *GenqlientDirective) error {
|
func (builder *typeBuilder) writeTypedef(
|
||||||
|
typedef *ast.Definition,
|
||||||
|
pos *ast.Position,
|
||||||
|
fields []field,
|
||||||
|
options *GenqlientDirective,
|
||||||
|
) error {
|
||||||
switch typedef.Kind {
|
switch typedef.Kind {
|
||||||
case ast.Object, ast.InputObject:
|
case ast.Object, ast.InputObject:
|
||||||
builder.WriteString("struct {\n")
|
builder.WriteString("struct {\n")
|
||||||
@@ -318,7 +337,7 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field
|
|||||||
|
|
||||||
case ast.Interface, ast.Union:
|
case ast.Interface, ast.Union:
|
||||||
if !allowBrokenFeatures {
|
if !allowBrokenFeatures {
|
||||||
return fmt.Errorf("not implemented: %v", typedef.Kind)
|
return errorf(pos, "not implemented: %v", typedef.Kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, write the interface type.
|
// First, write the interface type.
|
||||||
@@ -332,7 +351,7 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field
|
|||||||
// Then, write the implementations.
|
// Then, write the implementations.
|
||||||
// TODO(benkraft): Put a doc-comment somewhere with the list.
|
// TODO(benkraft): Put a doc-comment somewhere with the list.
|
||||||
for _, impldef := range builder.schema.GetPossibleTypes(typedef) {
|
for _, impldef := range builder.schema.GetPossibleTypes(typedef) {
|
||||||
name, err := builder.addTypeForDefinition(builder.typeNamePrefix, "", impldef, fields, options)
|
name, err := builder.addTypeForDefinition(builder.typeNamePrefix, "", impldef, pos, fields, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -355,8 +374,8 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field
|
|||||||
builder.WriteString(")\n")
|
builder.WriteString(")\n")
|
||||||
return nil
|
return nil
|
||||||
case ast.Scalar:
|
case ast.Scalar:
|
||||||
return fmt.Errorf("unknown scalar %v: please add it to genqlient.yaml", typedef.Name)
|
return errorf(pos, "unknown scalar %v: please add it to genqlient.yaml", typedef.Name)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected kind: %v", typedef.Kind)
|
return errorf(pos, "unexpected kind: %v", typedef.Kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user