Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gqlgen generates non compilable federation.go code when schema uses @requires directive with attribute referring inside array for dependency #2632

Open
mihirpmehta opened this issue May 10, 2023 · 2 comments
Labels
federation Related to Apollo federation help wanted Extra attention is needed

Comments

@mihirpmehta
Copy link

mihirpmehta commented May 10, 2023

What happened?

gqlgen generates non compilable code when schema has uses @requires directive with array nested attribute dependency for federation. in federation.go file i.e. see the sample schema that is mentioned below

What did you expect?

It should generate compilable code

Minimal graphql.schema and models to reproduce

type Query {
  reviews(bookID: ID!): [Review]
}
type Book @key(fields: "id", resolvable: false ) {
  id: ID! @external
  publishers:[Publishers] @external
  reviews: [Review]  @requires(fields: "publishers {name}" )
}
type Publishers @key(fields: "id", resolvable: false) {
  id: ID! @external
  name: String @external
}
type Review @key(fields: "id") {
  id: ID! @external
  name: String
}

versions

  • go run github.com/99designs/gqlgen version? v0.17.31
  • go version? go1.20.3 darwin/amd64

if you try to run ----

it will generate error validation failed: packages.Load: /graph/generated/federation.go:100 : 23: entity.Publishers.Name undefined (type []*model.Publishers has no field or method Name)

on this line in federation.go file

entity.Publishers.Name, err = ec.unmarshalOString2ᚖstring(ctx, rep["publishers"].(map[string]interface{})["name"])

This is because Publishers.Name is incorrect ... Publishers is array

@mihirpmehta mihirpmehta changed the title gqlgen generates non compilable code when schema has uses @requires directive with array nested attribute dependency for federation gqlgen generates non compilable code when schema uses @requires directive with attribute inside array for dependency in federation May 10, 2023
@mihirpmehta
Copy link
Author

The non compilable code is following

func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity {
	list := make([]fedruntime.Entity, len(representations))

	repsMap := map[string]struct {
		i []int
		r []map[string]interface{}
	}{}

	// We group entities by typename so that we can parallelize their resolution.
	// This is particularly helpful when there are entity groups in multi mode.
	buildRepresentationGroups := func(reps []map[string]interface{}) {
		for i, rep := range reps {
			typeName, ok := rep["__typename"].(string)
			if !ok {
				// If there is no __typename, we just skip the representation;
				// we just won't be resolving these unknown types.
				ec.Error(ctx, errors.New("__typename must be an existing string"))
				continue
			}

			_r := repsMap[typeName]
			_r.i = append(_r.i, i)
			_r.r = append(_r.r, rep)
			repsMap[typeName] = _r
		}
	}

	isMulti := func(typeName string) bool {
		switch typeName {
		default:
			return false
		}
	}

	resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (err error) {
		// we need to do our own panic handling, because we may be called in a
		// goroutine, where the usual panic handling can't catch us
		defer func() {
			if r := recover(); r != nil {
				err = ec.Recover(ctx, r)
			}
		}()

		switch typeName {
		case "Book":
			resolverName, err := entityResolverNameForBook(ctx, rep)
			if err != nil {
				return fmt.Errorf(`finding resolver for Entity "Book": %w`, err)
			}
			switch resolverName {

			case "findBookByID":
				id0, err := ec.unmarshalNID2string(ctx, rep["id"])
				if err != nil {
					return fmt.Errorf(`unmarshalling param 0 for findBookByID(): %w`, err)
				}
				entity, err := ec.resolvers.Entity().FindBookByID(ctx, id0)
				if err != nil {
					return fmt.Errorf(`resolving Entity "Book": %w`, err)
				}
                                 // THIS LINE IS INCORRECT
				entity.Publishers.Name, err = ec.unmarshalOString2ᚖstring(ctx, rep["publishers"].(map[string]interface{})["name"])
				if err != nil {
					return err
				}
				list[idx[i]] = entity
				return nil
			}
		case "Review":
			resolverName, err := entityResolverNameForReview(ctx, rep)
			if err != nil {
				return fmt.Errorf(`finding resolver for Entity "Review": %w`, err)
			}
			switch resolverName {

			case "findReviewByID":
				id0, err := ec.unmarshalNID2string(ctx, rep["id"])
				if err != nil {
					return fmt.Errorf(`unmarshalling param 0 for findReviewByID(): %w`, err)
				}
				entity, err := ec.resolvers.Entity().FindReviewByID(ctx, id0)
				if err != nil {
					return fmt.Errorf(`resolving Entity "Review": %w`, err)
				}

				list[idx[i]] = entity
				return nil
			}

		}
		return fmt.Errorf("%w: %s", ErrUnknownType, typeName)
	}

	resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) {
		// we need to do our own panic handling, because we may be called in a
		// goroutine, where the usual panic handling can't catch us
		defer func() {
			if r := recover(); r != nil {
				err = ec.Recover(ctx, r)
			}
		}()

		switch typeName {

		default:
			return errors.New("unknown type: " + typeName)
		}
	}

	resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) {
		if isMulti(typeName) {
			err := resolveManyEntities(ctx, typeName, reps, idx)
			if err != nil {
				ec.Error(ctx, err)
			}
		} else {
			// if there are multiple entities to resolve, parallelize (similar to
			// graphql.FieldSet.Dispatch)
			var e sync.WaitGroup
			e.Add(len(reps))
			for i, rep := range reps {
				i, rep := i, rep
				go func(i int, rep map[string]interface{}) {
					err := resolveEntity(ctx, typeName, rep, idx, i)
					if err != nil {
						ec.Error(ctx, err)
					}
					e.Done()
				}(i, rep)
			}
			e.Wait()
		}
	}
	buildRepresentationGroups(representations)

	switch len(repsMap) {
	case 0:
		return list
	case 1:
		for typeName, reps := range repsMap {
			resolveEntityGroup(typeName, reps.r, reps.i)
		}
		return list
	default:
		var g sync.WaitGroup
		g.Add(len(repsMap))
		for typeName, reps := range repsMap {
			go func(typeName string, reps []map[string]interface{}, idx []int) {
				resolveEntityGroup(typeName, reps, idx)
				g.Done()
			}(typeName, reps.r, reps.i)
		}
		g.Wait()
		return list
	}
}

@StevenACoffman StevenACoffman added help wanted Extra attention is needed federation Related to Apollo federation labels May 11, 2023
@mihirpmehta mihirpmehta changed the title gqlgen generates non compilable code when schema uses @requires directive with attribute inside array for dependency in federation gqlgen generates non compilable federation.go code when schema uses @requires directive with attribute referring inside array for dependency May 11, 2023
@dariuszkuc
Copy link
Contributor

fixed in #2884

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
federation Related to Apollo federation help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants