gqlgen: Generate imports wrong package and gives error

What happened?

When running go run github.com/99designs/gqlgen generate the generated resolver functions imports the wrong package.

if I use the github.com/pkg/errors library in my resolvers and then later on I trigger the generate command, gqlgen will remove this import and instead import the standard library’s errors package.

PRE go run github.com/99designs/gqlgen generate

// user.resolvers.go

import (
	"context"
	"github.com/pkg/errors"

	"github.com/foo/bar/app"
)

func (r *queryResolver) Me(ctx context.Context) (*app.User, error) {
	user,err := app.UserFromContext(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "no user in context")
	}
	return user, nil
}

POST go run github.com/99designs/gqlgen generate

validation failed: packages.Load: /github.com/foo/bar/graph/resolver/user.resolvers.go: Wrap not declared by package errors

Updated generated file…

// user.resolvers.go

import (
	"context"
	"errors"

	"github.com/foo/bar/app"
)

func (r *queryResolver) Me(ctx context.Context) (*app.User, error) {
	user,err := app.UserFromContext(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "no user in context")
	}
	return user, nil
}

So even though stdlib errors pkg doesn’t have a Wrap function, it will still be added to the import statement rather than using the one that was previously declared github.com/pkg/errors followed by reporting that the function is not declared in that package.

gqlconfig.yml

schema:
  - "graph/schema/**/*.graphql"

exec:
  filename: graph/generated/generated.go
  package: generated

resolver:
  layout: follow-schema
  dir: graph/resolver
  package: resolver


struct_tag: gql
omit_slice_element_pointers: true

autobind:
  - github.com/foo/bar/app

What did you expect?

import / use the already declared package rather than attempting to import it’s own

Minimal graphql.schema and models to reproduce

# schema/schema.graphql
schema {
    query: Query
    mutation: Mutation
}

type Query

type Mutation

# schema/types/user.graphql
extend type Query {
    me: User
}
type User {
    id: ID!
    email: String!
    name: String
}

versions

  • gqlgen version? v0.11.3
  • go version? 1.14
  • dep or go modules? go mod

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 31
  • Comments: 24 (2 by maintainers)

Commits related to this issue

Most upvoted comments

hey guys, any update on this ?

Bump. This is a genuine issue and none of the mentioned fixes are really satisfactory. I’m happy to work on a PR if someone can tell me it’s okay to make a configuration option for replacing packages Alternatively, would be nice to know why golang.org/x/tools/imports was made to not touch imports and a custom implementation was made instead. I’ll mention @vektah who is the author for the commit breml mentioned. Hopefully we can get this moving 🙂

An alternative to @duckbrain his solution it to use gofmt to update the imports. Just run the following at the root of your project after you (re-)generated the files:

gofmt -w -d -r '"errors" -> "github.com/pkg/errors"' **/*.go

As a hacky workaround until this gets fixed, I created a plugin to remove the line and re-run goimports after it generates the errant file.

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/99designs/gqlgen/api"
	"github.com/99designs/gqlgen/codegen"
	"github.com/99designs/gqlgen/codegen/config"
)

type ImportsPlugin struct {
}

func (ImportsPlugin) Name() string {
	return "Imports"
}
func (ImportsPlugin) GenerateCode(cfg *codegen.Data) error {
	dir := cfg.Config.Resolver.Dir()

	badLineRegexp := regexp.MustCompile(`\n\s*"errors"\n`)

	return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !strings.HasSuffix(info.Name(), ".resolvers.go") {
			return nil
		}
		f, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}
		if !badLineRegexp.Match(f) {
			return nil
		}
		n := badLineRegexp.ReplaceAll(f, []byte("\n"))
		err = ioutil.WriteFile(path, n, os.ModePerm)
		if err != nil {
			return err
		}
		c := exec.Command("go", "run", "golang.org/x/tools/cmd/goimports", "-w", path)
		c.Stdout = os.Stdout
		c.Stderr = os.Stderr
		return c.Run()
	})
}

func main() {
	cfg, err := config.LoadConfigFromDefaultLocations()
	if err != nil {
		fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
		os.Exit(2)
	}

	err = api.Generate(cfg,
		api.AddPlugin(ImportsPlugin{}),
	)
	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(3)
	}
}

@pjvds many users may not want to use “github.com/pkg/errors” and GQLGen has no way of knowing that github.com/pkg/errors is a drop-in replacement for errors. @j0hnsmith’s workaround seems pretty reasonable to me.

The general problem is GQLGen may request an import who’s name conflicts with an existing import. The best way I could think to solve the problem is add a configuration option to map imports to a replacement, kind of like a replace directive in go.mod, but it replaces the generated import. It would be up to the user to ensure the replaced package is compatible with GQLGen’s usage of it.

See: https://github.com/99designs/gqlgen/blob/5ad012e3d7be1127706b9c8a3da0378df3a98ec1/codegen/templates/import.go#L43 where the import is handled.

Alternatively the specific problem was introduced by the resolvers template moving from panic("not implemented") to panic(errors.Errorf("not implemented")).

it seems that adding an alias to the import makes gqlgen ignore it, I use that as a workaround

Adding an alias seems to make said aliased imports get removed entirely, in my case.

Edit: Spoke too soon, seems to work for some packages but not others. "github.com/99designs/gqlgen/graphql" for example, if aliased, gets removed.

I wonder why gqlgen does insertions and deletions of the imports on its own in contrast to relying on golang.org/x/tools/imports, which is purely made for this purpose, used with great success in most IDE/editors and is maintained by the Go maintainers.

The pruning happens in https://github.com/99designs/gqlgen/blob/master/internal/imports/prune.go and while golang.org/x/tools/imports is used, it is forced to not touch the imports (see: https://github.com/99designs/gqlgen/blob/master/internal/imports/prune.go#L46). The respective commit (https://github.com/99designs/gqlgen/commit/732be3959b402bbd3b864c5f40f475640f1334c5) states “Don’t let goimports guess import paths” in the commit message, but does not give a reason for this decision nor a reference to an issue.

Wouldn’t this issue be resolved, if the line {{ reserveImport "errors" }} (https://github.com/99designs/gqlgen/blob/master/plugin/resolvergen/resolver.gotpl#L7) is removed and golang.org/x/tools/imports would not be be limited to formatting only?

https://github.com/99designs/gqlgen/blob/master/plugin/resolvergen/resolver.gotpl#L7

del this will resolve this issue. Why add lines:

{{ reserveImport "context"  }}
{{ reserveImport "fmt"  }}
{{ reserveImport "io"  }}
{{ reserveImport "strconv"  }}
{{ reserveImport "time"  }}
{{ reserveImport "sync"  }}
{{ reserveImport "errors"  }}
{{ reserveImport "bytes"  }}

fmt aliase f image

after gqlgen generate, f was deleted image

Running gofmt doesn’t fix the imports for me 😦