gqlgen icon indicating copy to clipboard operation
gqlgen copied to clipboard

Generate imports wrong package and gives error

Open randallmlough opened this issue 4 years ago • 24 comments

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

randallmlough avatar Apr 27 '20 21:04 randallmlough

I have this problem too. import "github.com/pkg/errors" not work

zhyq0826 avatar Jun 10 '20 10:06 zhyq0826

+1 . Any plan?

exfly avatar Jul 23 '20 01:07 exfly

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"  }}

exfly avatar Jul 23 '20 01:07 exfly

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)
	}
}

duckbrain avatar Aug 17 '20 18:08 duckbrain

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Nov 15 '20 19:11 stale[bot]

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

pjvds avatar Jan 18 '21 15:01 pjvds

+1

mingrammer avatar Jul 19 '21 14:07 mingrammer

+1

jafar9 avatar Aug 13 '21 04:08 jafar9

This is still a problem however I've found what seems like a good workaround, simply import as

werrors "github.com/pkg/errors"

and it doesn't get changed to "errors".

j0hnsmith avatar Sep 08 '21 12:09 j0hnsmith

@j0hnsmith it is a nice workaround, but it requires you to write a custom import in every file where you need that errors package. It will also not work with auto import tooling. Since you run the generation command anyway, it is easy to just run the gofmt command to do the rewrite for you.

pjvds avatar Sep 08 '21 13:09 pjvds

Ahh, I see. I'm totally new to gqlgen, just running commands in a Makefile. The command exits with status 1 so I assumed that it was unsuccessful however I now understand that the code was generated ok, I assume it's only the post generation validation step that fails (which can be corrected by running gofmt).

@mtibben This is still a problem, can you reopen this issue to help others find it. Also would be good to add a .github/stale.yml with a exemptLabels and add a doNotClose label to prevent this issue from being closed.

The other reserveImports look reasonable however github.com/pkg/errors is commonly used, what would the impact of removing {{ reserveImport "errors" }} be?

j0hnsmith avatar Sep 08 '21 14:09 j0hnsmith

Running gofmt doesn't fix the imports for me :(

j0hnsmith avatar Sep 08 '21 14:09 j0hnsmith

Running gofmt doesn't fix the imports for me :(

Did you ran this from the root without the werror import?:

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

pjvds avatar Sep 08 '21 18:09 pjvds

It is open again!! Thanks @mtibben 👍🏻

Lets improve on this one. I have a personal branch with some fixes that might be interesting to pull. It just switches to the github.com/pkg/errors package. Not sure if we need anything more than that?

pjvds avatar Sep 08 '21 19:09 pjvds

@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")).

duckbrain avatar Sep 09 '21 16:09 duckbrain

Did you ran this from the root without the werror import?:

@pjvds I've realised the command I ran was incorrect, the one you provided does work for me.

After discussing it with a colleague who is more familiar with this stuff, we're going to go down the werrors route as it's a 'one time' fix (per file) for what we have (until we add new schemas).

j0hnsmith avatar Sep 09 '21 16:09 j0hnsmith

fmt aliase f image

after gqlgen generate, f was deleted image

day112 avatar Oct 28 '21 06:10 day112

hey guys, any update on this ?

nxtcoder17 avatar Apr 07 '22 03:04 nxtcoder17

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?

breml avatar Jul 05 '22 08:07 breml

hey guys, anyone found a fix for this ?

MelvinKim avatar Sep 09 '22 16:09 MelvinKim

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 :slightly_smiling_face:

Feyko avatar Oct 13 '22 22:10 Feyko

Bump. Been about 6 months since the last bump. Any have any updates? Has there been any traction on this fix or a workaround more suitable than gofmt after generate?

nanno77 avatar Apr 02 '23 22:04 nanno77

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

albesko avatar Sep 05 '23 14:09 albesko

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.

adamhostettler avatar Oct 12 '23 20:10 adamhostettler