diff --git a/example/federation/accounts/graph/generated/federation.go b/example/federation/accounts/graph/generated/federation.go index 468fabb3195..95516956e94 100644 --- a/example/federation/accounts/graph/generated/federation.go +++ b/example/federation/accounts/graph/generated/federation.go @@ -9,10 +9,15 @@ import ( "strings" "sync" - "github.com/99designs/gqlgen/example/federation/accounts/graph/model" "github.com/99designs/gqlgen/plugin/federation/fedruntime" ) +var ( + ErrUnknownType = errors.New("unknown type") + ErrTypeNotFound = errors.New("type not found") + ErrUnableToResolveEntity = errors.New("unable to resolve Entity") +) + func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { return fedruntime.Service{}, errors.New("federated introspection disabled") @@ -77,46 +82,43 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "EmailHost": - entity, err := func() (*model.EmailHost, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindEmailHostByID(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForEmailHost(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "EmailHost": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["id"]) if err != nil { - return fmt.Errorf(`resolving Entity "EmailHost": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "EmailHost"`) + entity, err := ec.resolvers.Entity().FindEmailHostByID(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "EmailHost": %w`, err) } list[idx[i]] = entity return nil case "User": - entity, err := func() (*model.User, error) { - id0, err := ec.unmarshalNID2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindUserByID(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForUser(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "User": %w`, err) + } + id0, err := ec.unmarshalNID2string(ctx, rep["id"]) if err != nil { - return fmt.Errorf(`resolving Entity "User": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "User"`) + entity, err := ec.resolvers.Entity().FindUserByID(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "User": %w`, err) } list[idx[i]] = entity return nil - default: - return errors.New("unknown type: " + typeName) } + return fmt.Errorf("%w: %s", ErrUnknownType, typeName) } resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { @@ -182,3 +184,39 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func (ec *executionContext) entityResolverNameForEmailHost(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["id"]; !ok { + return "", errors.New(`missing parameter "id"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "id": %w`, err) + } + return "findEmailHostByID", nil +} + +func (ec *executionContext) entityResolverNameForUser(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["id"]; !ok { + return "", errors.New(`missing parameter "id"`) + } + + if _, err := ec.unmarshalNID2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "id": %w`, err) + } + return "findUserByID", nil +} diff --git a/example/federation/products/graph/generated/federation.go b/example/federation/products/graph/generated/federation.go index 5443fd9028e..96106fe3ef5 100644 --- a/example/federation/products/graph/generated/federation.go +++ b/example/federation/products/graph/generated/federation.go @@ -9,10 +9,15 @@ import ( "strings" "sync" - "github.com/99designs/gqlgen/example/federation/products/graph/model" "github.com/99designs/gqlgen/plugin/federation/fedruntime" ) +var ( + ErrUnknownType = errors.New("unknown type") + ErrTypeNotFound = errors.New("type not found") + ErrUnableToResolveEntity = errors.New("unable to resolve Entity") +) + func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { return fedruntime.Service{}, errors.New("federated introspection disabled") @@ -77,59 +82,63 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "Manufacturer": - entity, err := func() (*model.Manufacturer, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindManufacturerByID(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForManufacturer(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Manufacturer": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["id"]) if err != nil { - return fmt.Errorf(`resolving Entity "Manufacturer": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "Manufacturer"`) + entity, err := ec.resolvers.Entity().FindManufacturerByID(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "Manufacturer": %w`, err) } list[idx[i]] = entity return nil case "Product": - entity, err := func() (*model.Product, error) { + resolverName, err := ec.entityResolverNameForProduct(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Product": %w`, err) + } + + switch resolverName { + + case "findProductByManufacturerIDAndID": id0, err := ec.unmarshalNString2string(ctx, rep["manufacturer"].(map[string]interface{})["id"]) - if err == nil { - id1, err := ec.unmarshalNString2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindProductByManufacturerIDAndID(ctx, id0, id1) - } + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findProductByManufacturerIDAndID(): %w`, err) + } + id1, err := ec.unmarshalNString2string(ctx, rep["id"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for findProductByManufacturerIDAndID(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindProductByManufacturerIDAndID(ctx, id0, id1) + if err != nil { + return fmt.Errorf(`resolving Entity "Product": %w`, err) } - return nil, nil - }() - - if entity == nil { - entity, err = func() (*model.Product, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["upc"]) - if err == nil { - return ec.resolvers.Entity().FindProductByUpc(ctx, id0) - } - return nil, nil - }() - } - if err != nil { - return fmt.Errorf(`resolving Entity "Product": %w`, err) - } - if entity == nil { - return errors.New(`unable to resolve Entity "Product"`) - } + list[idx[i]] = entity + return nil + case "findProductByUpc": + id0, err := ec.unmarshalNString2string(ctx, rep["upc"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findProductByUpc(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindProductByUpc(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "Product": %w`, err) + } - list[idx[i]] = entity - return nil + list[idx[i]] = entity + return nil + } - default: - return errors.New("unknown type: " + typeName) } + return fmt.Errorf("%w: %s", ErrUnknownType, typeName) } resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { @@ -195,3 +204,68 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func (ec *executionContext) entityResolverNameForManufacturer(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["id"]; !ok { + return "", errors.New(`missing parameter "id"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "id": %w`, err) + } + return "findManufacturerByID", nil +} + +func (ec *executionContext) entityResolverNameForProduct(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + for { + m = rep + if val, ok = m["manufacturer"]; !ok { + break + } + + if m, ok = val.(map[string]interface{}); !ok { + break + } + if val, ok = m["id"]; !ok { + break + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + break + } + m = rep + if val, ok = m["id"]; !ok { + break + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + break + } + return "findProductByManufacturerIDAndID", nil + } + for { + m = rep + if val, ok = m["upc"]; !ok { + break + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + break + } + return "findProductByUpc", nil + } + return "", fmt.Errorf("%w for Product", ErrTypeNotFound) +} diff --git a/example/federation/reviews/graph/generated/federation.go b/example/federation/reviews/graph/generated/federation.go index ab50002cc95..874b43df0f5 100644 --- a/example/federation/reviews/graph/generated/federation.go +++ b/example/federation/reviews/graph/generated/federation.go @@ -9,10 +9,15 @@ import ( "strings" "sync" - "github.com/99designs/gqlgen/example/federation/reviews/graph/model" "github.com/99designs/gqlgen/plugin/federation/fedruntime" ) +var ( + ErrUnknownType = errors.New("unknown type") + ErrTypeNotFound = errors.New("type not found") + ErrUnableToResolveEntity = errors.New("unable to resolve Entity") +) + func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { return fedruntime.Service{}, errors.New("federated introspection disabled") @@ -77,59 +82,55 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "Product": - entity, err := func() (*model.Product, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["manufacturer"].(map[string]interface{})["id"]) - if err == nil { - id1, err := ec.unmarshalNString2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindProductByManufacturerIDAndID(ctx, id0, id1) - } - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForProduct(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Product": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["manufacturer"].(map[string]interface{})["id"]) if err != nil { - return fmt.Errorf(`resolving Entity "Product": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) + } + id1, err := ec.unmarshalNString2string(ctx, rep["id"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "Product"`) + entity, err := ec.resolvers.Entity().FindProductByManufacturerIDAndID(ctx, id0, id1) + if err != nil { + return fmt.Errorf(`resolving Entity "Product": %w`, err) } list[idx[i]] = entity return nil case "User": - entity, err := func() (*model.User, error) { - id0, err := ec.unmarshalNID2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindUserByID(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForUser(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "User": %w`, err) + } + id0, err := ec.unmarshalNID2string(ctx, rep["id"]) if err != nil { - return fmt.Errorf(`resolving Entity "User": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "User"`) + entity, err := ec.resolvers.Entity().FindUserByID(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "User": %w`, err) } entity.Host.ID, err = ec.unmarshalNString2string(ctx, rep["host"].(map[string]interface{})["id"]) if err != nil { return err } - entity.Email, err = ec.unmarshalNString2string(ctx, rep["email"]) if err != nil { return err } - list[idx[i]] = entity return nil - default: - return errors.New("unknown type: " + typeName) } + return fmt.Errorf("%w: %s", ErrUnknownType, typeName) } resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { @@ -195,3 +196,54 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func (ec *executionContext) entityResolverNameForProduct(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["manufacturer"]; !ok { + return "", errors.New(`missing parameter "manufacturer"`) + } + + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "manufacturer" is not a struct`) + } + if val, ok = m["id"]; !ok { + return "", errors.New(`missing parameter "id"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "manufacturer.id": %w`, err) + } + m = rep + if val, ok = m["id"]; !ok { + return "", errors.New(`missing parameter "id"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "id": %w`, err) + } + return "findProductByManufacturerIDAndID", nil +} + +func (ec *executionContext) entityResolverNameForUser(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["id"]; !ok { + return "", errors.New(`missing parameter "id"`) + } + + if _, err := ec.unmarshalNID2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "id": %w`, err) + } + return "findUserByID", nil +} diff --git a/plugin/federation/federation.go b/plugin/federation/federation.go index 36a24a02533..65d8cee1907 100644 --- a/plugin/federation/federation.go +++ b/plugin/federation/federation.go @@ -2,7 +2,6 @@ package federation import ( "fmt" - "go/types" "sort" "strings" @@ -17,8 +16,6 @@ import ( type federation struct { Entities []*Entity - - imports []string } // New returns a federation plugin that injects @@ -161,9 +158,7 @@ type Entity struct { Def *ast.Definition Resolvers []*EntityResolver Requires []*Requires - // Type *config.TypeReference // The Go representation of that field type - Type string // The Go representation of that field type - Multi bool + Multi bool } type EntityResolver struct { @@ -201,16 +196,6 @@ func (f *federation) GenerateCode(data *codegen.Data) error { } for _, e := range f.Entities { obj := data.Objects.ByName(e.Def.Name) - e.Type = types.TypeString(obj.Type, func(i *types.Package) string { - f.imports = append(f.imports, i.Path()) - if pkg := data.Config.Packages.NameForPackage(i.Path()); pkg != data.Config.Federation.Package { - return pkg - } - return "" - }) - if e.Type == "" { - panic("unknown type " + obj.Type.String()) - } for _, r := range e.Resolvers { // fill in types for key fields @@ -247,13 +232,6 @@ func (f *federation) GenerateCode(data *codegen.Data) error { }) } -func (f *federation) Imports() string { - for _, path := range f.imports { - _, _ = templates.CurrentImports.Reserve(path) - } - return "" -} - func (f *federation) setEntities(schema *ast.Schema) { for _, schemaType := range schema.Types { if schemaType.Kind == ast.Interface { diff --git a/plugin/federation/federation.gotpl b/plugin/federation/federation.gotpl index c73ab005573..dcd364de113 100644 --- a/plugin/federation/federation.gotpl +++ b/plugin/federation/federation.gotpl @@ -6,7 +6,11 @@ {{ reserveImport "github.com/99designs/gqlgen/plugin/federation/fedruntime" }} -{{ .Imports }} +var ( + ErrUnknownType = errors.New("unknown type") + ErrTypeNotFound = errors.New("type not found") + ErrUnableToResolveEntity = errors.New("unable to resolve Entity") +) func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { @@ -83,43 +87,61 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati {{ range $_, $entity := .Entities }} {{- if .Resolvers -}} {{ if not .Multi -}} - case "{{.Def.Name}}": - {{range $i, $_ := .Resolvers -}} - {{- if ne $i 0 -}}if entity == nil { {{- end -}} - entity, err {{- if eq $i 0 -}}:{{- end -}}= func() (*{{$entity.Type}}, error) { - {{- range $j, $keyField := .KeyFields -}} + case "{{.Def.Name}}": + resolverName, err := ec.entityResolverNameFor{{.Def.Name}}(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err) + } + {{ if eq (len .Resolvers) 1 }} + {{ $resolver := (index $entity.Resolvers 0)}} + {{- range $j, $keyField := $resolver.KeyFields }} + id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) + if err != nil { + return fmt.Errorf(`unmarshalling param {{$j}} for %s(): %w`, resolverName, err) + } + {{- end}} + entity, err := ec.resolvers.Entity().{{$resolver.ResolverName | go}}(ctx, {{- range $j, $_ := $resolver.KeyFields -}} id{{$j}}, {{end}}) + if err != nil { + return fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err) + } + {{ range $entity.Requires }} + entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) + if err != nil { + return err + } + {{- end }} + list[idx[i]] = entity + return nil + {{ else }} + switch resolverName { + {{ range $i, $resolver := .Resolvers }} + case "{{.ResolverName}}": + {{- range $j, $keyField := .KeyFields }} id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) - if err == nil { - {{- end}} - return ec.resolvers.Entity().{{.ResolverName | go}}(ctx, {{- range $j, $_ := .KeyFields -}} id{{$j}}, {{end}}) - {{- range .KeyFields -}} + if err != nil { + return fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err) } {{- end}} - return nil, nil - }() - {{ if ne $i 0 -}} } {{- end}} - {{end}} - if err != nil { - return fmt.Errorf(`resolving Entity "{{.Def.Name}}": %w`, err) - } - if entity == nil { - return errors.New(`unable to resolve Entity "{{.Def.Name}}"`) - } - {{ range .Requires }} - entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) - if err != nil { - return err - } - {{ end }} - - list[idx[i]] = entity - return nil + entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, {{- range $j, $_ := .KeyFields -}} id{{$j}}, {{end}}) + if err != nil { + return fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err) + } + {{ range $entity.Requires }} + entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) + if err != nil { + return err + } + {{- end }} + list[idx[i]] = entity + return nil + {{- end }} + } + {{ end }} {{ end }} {{ end }} {{- end }} - default: - return errors.New("unknown type: "+typeName) } + return fmt.Errorf("%w: %s", ErrUnknownType, typeName) } resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { @@ -227,4 +249,64 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +{{- /* Make sure the required fields are in the given entity representation and return the name of the proper resolver. */ -}} + +{{ range $_, $entity := .Entities }} + {{- if .Resolvers }} + + func (ec *executionContext) entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + {{ if eq (len .Resolvers) 1 }} + {{ $resolver := (index $entity.Resolvers 0)}} + {{- range $_, $keyField := $resolver.KeyFields }} + m = rep + {{- range $i, $field := .Field }} + if val, ok = m["{{.}}"]; !ok { + return "", errors.New(`missing parameter "{{.}}"`) + } + {{ if (ne $i $keyField.Field.LastIndex ) }} + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "{{.}}" is not a struct`) + } + {{- else }} + {{- end}} + {{- end}} + if _, err := ec.{{.Type.UnmarshalFunc}}(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "{{.Field.Join `.`}}": %w`, err) + } + {{- end }} + return "{{$resolver.ResolverName}}", nil + {{- else }} + {{ range .Resolvers }} + for { + {{- range $_, $keyField := .KeyFields }} + m = rep + {{- range $i, $field := .Field }} + if val, ok = m["{{.}}"]; !ok { + break + } + {{ if (ne $i $keyField.Field.LastIndex ) }} + if m, ok = val.(map[string]interface{}); !ok { + break + } + {{- end}} + {{- end}} + if _, err := ec.{{.Type.UnmarshalFunc}}(ctx, val); err != nil { + break + } + {{- end }} + return "{{.ResolverName}}", nil + } + {{- end }} + return "", fmt.Errorf("%w for {{$entity.Name}}", ErrTypeNotFound) + {{ end -}} + } + {{- end }} +{{- end }} + {{end}} diff --git a/plugin/federation/federation_entityresolver_test.go b/plugin/federation/federation_entityresolver_test.go index 23959d08838..00c76d5cd8d 100644 --- a/plugin/federation/federation_entityresolver_test.go +++ b/plugin/federation/federation_entityresolver_test.go @@ -147,6 +147,45 @@ func TestEntityResolver(t *testing.T) { require.Equal(t, resp.Entities[1].Hello.Name, "world name - 2") }) + t.Run("World entities with multiple keys", func(t *testing.T) { + representations := []map[string]interface{}{ + { + "__typename": "WorldWithMultipleKeys", + "hello": map[string]interface{}{ + "name": "world name - 1", + }, + "foo": "foo 1", + }, { + "__typename": "WorldWithMultipleKeys", + "bar": 11, + }, + } + + var resp struct { + Entities []struct { + Foo string `json:"foo"` + Hello struct { + Name string `json:"name"` + } `json:"hello"` + Bar int `json:"bar"` + } `json:"_entities"` + } + + err := c.Post( + entityQuery([]string{ + "WorldWithMultipleKeys {foo hello {name}}", + "WorldWithMultipleKeys {bar}", + }), + &resp, + client.Var("representations", representations), + ) + + require.NoError(t, err) + require.Equal(t, resp.Entities[0].Foo, "foo 1") + require.Equal(t, resp.Entities[0].Hello.Name, "world name - 1") + require.Equal(t, resp.Entities[1].Bar, 11) + }) + t.Run("Hello WorldName entities (heterogeneous)", func(t *testing.T) { // Entity resolution can handle heterogenenous representations. Meaning, // the representations for resolving entities can be of different diff --git a/plugin/federation/fieldset/fieldset.go b/plugin/federation/fieldset/fieldset.go index a7157ed673e..35616cbea3e 100644 --- a/plugin/federation/fieldset/fieldset.go +++ b/plugin/federation/fieldset/fieldset.go @@ -131,6 +131,10 @@ func (f Field) JoinGo(str string) string { return strings.Join(strs, str) } +func (f Field) LastIndex() int { + return len(f) - 1 +} + // local functions // parseUnnestedKeyFieldSet // handles simple case where none of the fields are nested. diff --git a/plugin/federation/testdata/allthethings/generated/federation.go b/plugin/federation/testdata/allthethings/generated/federation.go index 6cf0fe8cd37..c037f6abd2b 100644 --- a/plugin/federation/testdata/allthethings/generated/federation.go +++ b/plugin/federation/testdata/allthethings/generated/federation.go @@ -10,7 +10,12 @@ import ( "sync" "github.com/99designs/gqlgen/plugin/federation/fedruntime" - "github.com/99designs/gqlgen/plugin/federation/testdata/allthethings/model" +) + +var ( + ErrUnknownType = errors.New("unknown type") + ErrTypeNotFound = errors.New("type not found") + ErrUnableToResolveEntity = errors.New("unable to resolve Entity") ) func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { @@ -77,138 +82,141 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "ExternalExtension": - entity, err := func() (*model.ExternalExtension, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["upc"]) - if err == nil { - return ec.resolvers.Entity().FindExternalExtensionByUpc(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForExternalExtension(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "ExternalExtension": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["upc"]) if err != nil { - return fmt.Errorf(`resolving Entity "ExternalExtension": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "ExternalExtension"`) + entity, err := ec.resolvers.Entity().FindExternalExtensionByUpc(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "ExternalExtension": %w`, err) } list[idx[i]] = entity return nil case "Hello": - entity, err := func() (*model.Hello, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindHelloByName(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForHello(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Hello": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["name"]) if err != nil { - return fmt.Errorf(`resolving Entity "Hello": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "Hello"`) + entity, err := ec.resolvers.Entity().FindHelloByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "Hello": %w`, err) } list[idx[i]] = entity return nil case "NestedKey": - entity, err := func() (*model.NestedKey, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["id"]) - if err == nil { - id1, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) - if err == nil { - return ec.resolvers.Entity().FindNestedKeyByIDAndHelloName(ctx, id0, id1) - } - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForNestedKey(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "NestedKey": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["id"]) if err != nil { - return fmt.Errorf(`resolving Entity "NestedKey": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) + } + id1, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "NestedKey"`) + entity, err := ec.resolvers.Entity().FindNestedKeyByIDAndHelloName(ctx, id0, id1) + if err != nil { + return fmt.Errorf(`resolving Entity "NestedKey": %w`, err) } list[idx[i]] = entity return nil case "VeryNestedKey": - entity, err := func() (*model.VeryNestedKey, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["id"]) - if err == nil { - id1, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) - if err == nil { - id2, err := ec.unmarshalNString2string(ctx, rep["world"].(map[string]interface{})["foo"]) - if err == nil { - id3, err := ec.unmarshalNInt2int(ctx, rep["world"].(map[string]interface{})["bar"]) - if err == nil { - id4, err := ec.unmarshalNString2string(ctx, rep["more"].(map[string]interface{})["world"].(map[string]interface{})["foo"]) - if err == nil { - return ec.resolvers.Entity().FindVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo(ctx, id0, id1, id2, id3, id4) - } - } - } - } - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForVeryNestedKey(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "VeryNestedKey": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["id"]) if err != nil { - return fmt.Errorf(`resolving Entity "VeryNestedKey": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) + } + id1, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for %s(): %w`, resolverName, err) + } + id2, err := ec.unmarshalNString2string(ctx, rep["world"].(map[string]interface{})["foo"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 2 for %s(): %w`, resolverName, err) + } + id3, err := ec.unmarshalNInt2int(ctx, rep["world"].(map[string]interface{})["bar"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 3 for %s(): %w`, resolverName, err) + } + id4, err := ec.unmarshalNString2string(ctx, rep["more"].(map[string]interface{})["world"].(map[string]interface{})["foo"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 4 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "VeryNestedKey"`) + entity, err := ec.resolvers.Entity().FindVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo(ctx, id0, id1, id2, id3, id4) + if err != nil { + return fmt.Errorf(`resolving Entity "VeryNestedKey": %w`, err) } entity.ID, err = ec.unmarshalNString2string(ctx, rep["id"]) if err != nil { return err } - entity.Hello.Secondary, err = ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["secondary"]) if err != nil { return err } - list[idx[i]] = entity return nil case "World": - entity, err := func() (*model.World, error) { + resolverName, err := ec.entityResolverNameForWorld(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "World": %w`, err) + } + + switch resolverName { + + case "findWorldByFoo": id0, err := ec.unmarshalNString2string(ctx, rep["foo"]) - if err == nil { - return ec.resolvers.Entity().FindWorldByFoo(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findWorldByFoo(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindWorldByFoo(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "World": %w`, err) } - return nil, nil - }() - - if entity == nil { - entity, err = func() (*model.World, error) { - id0, err := ec.unmarshalNInt2int(ctx, rep["bar"]) - if err == nil { - return ec.resolvers.Entity().FindWorldByBar(ctx, id0) - } - return nil, nil - }() - } - if err != nil { - return fmt.Errorf(`resolving Entity "World": %w`, err) - } - if entity == nil { - return errors.New(`unable to resolve Entity "World"`) - } + list[idx[i]] = entity + return nil + case "findWorldByBar": + id0, err := ec.unmarshalNInt2int(ctx, rep["bar"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findWorldByBar(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindWorldByBar(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "World": %w`, err) + } - list[idx[i]] = entity - return nil + list[idx[i]] = entity + return nil + } - default: - return errors.New("unknown type: " + typeName) } + return fmt.Errorf("%w: %s", ErrUnknownType, typeName) } resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { @@ -274,3 +282,189 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func (ec *executionContext) entityResolverNameForExternalExtension(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["upc"]; !ok { + return "", errors.New(`missing parameter "upc"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "upc": %w`, err) + } + return "findExternalExtensionByUpc", nil +} + +func (ec *executionContext) entityResolverNameForHello(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "name": %w`, err) + } + return "findHelloByName", nil +} + +func (ec *executionContext) entityResolverNameForNestedKey(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["id"]; !ok { + return "", errors.New(`missing parameter "id"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "id": %w`, err) + } + m = rep + if val, ok = m["hello"]; !ok { + return "", errors.New(`missing parameter "hello"`) + } + + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "hello" is not a struct`) + } + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "hello.name": %w`, err) + } + return "findNestedKeyByIDAndHelloName", nil +} + +func (ec *executionContext) entityResolverNameForVeryNestedKey(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["id"]; !ok { + return "", errors.New(`missing parameter "id"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "id": %w`, err) + } + m = rep + if val, ok = m["hello"]; !ok { + return "", errors.New(`missing parameter "hello"`) + } + + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "hello" is not a struct`) + } + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "hello.name": %w`, err) + } + m = rep + if val, ok = m["world"]; !ok { + return "", errors.New(`missing parameter "world"`) + } + + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "world" is not a struct`) + } + if val, ok = m["foo"]; !ok { + return "", errors.New(`missing parameter "foo"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "world.foo": %w`, err) + } + m = rep + if val, ok = m["world"]; !ok { + return "", errors.New(`missing parameter "world"`) + } + + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "world" is not a struct`) + } + if val, ok = m["bar"]; !ok { + return "", errors.New(`missing parameter "bar"`) + } + + if _, err := ec.unmarshalNInt2int(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "world.bar": %w`, err) + } + m = rep + if val, ok = m["more"]; !ok { + return "", errors.New(`missing parameter "more"`) + } + + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "more" is not a struct`) + } + if val, ok = m["world"]; !ok { + return "", errors.New(`missing parameter "world"`) + } + + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "world" is not a struct`) + } + if val, ok = m["foo"]; !ok { + return "", errors.New(`missing parameter "foo"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "more.world.foo": %w`, err) + } + return "findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo", nil +} + +func (ec *executionContext) entityResolverNameForWorld(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + for { + m = rep + if val, ok = m["foo"]; !ok { + break + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + break + } + return "findWorldByFoo", nil + } + for { + m = rep + if val, ok = m["bar"]; !ok { + break + } + + if _, err := ec.unmarshalNInt2int(ctx, val); err != nil { + break + } + return "findWorldByBar", nil + } + return "", fmt.Errorf("%w for World", ErrTypeNotFound) +} diff --git a/plugin/federation/testdata/entityresolver/entity.resolvers.go b/plugin/federation/testdata/entityresolver/entity.resolvers.go index 6c0f238bde9..0a2ff3fcff2 100644 --- a/plugin/federation/testdata/entityresolver/entity.resolvers.go +++ b/plugin/federation/testdata/entityresolver/entity.resolvers.go @@ -85,6 +85,21 @@ func (r *entityResolver) FindWorldNameByName(ctx context.Context, name string) ( }, nil } +func (r *entityResolver) FindWorldWithMultipleKeysByHelloNameAndFoo(ctx context.Context, helloName string, foo string) (*generated.WorldWithMultipleKeys, error) { + return &generated.WorldWithMultipleKeys{ + Hello: &generated.Hello{ + Name: helloName, + }, + Foo: foo, + }, nil +} + +func (r *entityResolver) FindWorldWithMultipleKeysByBar(ctx context.Context, bar int) (*generated.WorldWithMultipleKeys, error) { + return &generated.WorldWithMultipleKeys{ + Bar: bar, + }, nil +} + // Entity returns generated.EntityResolver implementation. func (r *Resolver) Entity() generated.EntityResolver { return &entityResolver{r} } diff --git a/plugin/federation/testdata/entityresolver/generated/exec.go b/plugin/federation/testdata/entityresolver/generated/exec.go index c15b7881858..23ba9f3fb7d 100644 --- a/plugin/federation/testdata/entityresolver/generated/exec.go +++ b/plugin/federation/testdata/entityresolver/generated/exec.go @@ -45,14 +45,16 @@ type DirectiveRoot struct { type ComplexityRoot struct { Entity struct { - FindHelloByName func(childComplexity int, name string) int - FindHelloWithErrorsByName func(childComplexity int, name string) int - FindManyMultiHelloByNames func(childComplexity int, reps []*MultiHelloByNamesInput) int - FindManyMultiHelloWithErrorByNames func(childComplexity int, reps []*MultiHelloWithErrorByNamesInput) int - FindPlanetRequiresByName func(childComplexity int, name string) int - FindPlanetRequiresNestedByName func(childComplexity int, name string) int - FindWorldByHelloNameAndFoo func(childComplexity int, helloName string, foo string) int - FindWorldNameByName func(childComplexity int, name string) int + FindHelloByName func(childComplexity int, name string) int + FindHelloWithErrorsByName func(childComplexity int, name string) int + FindManyMultiHelloByNames func(childComplexity int, reps []*MultiHelloByNamesInput) int + FindManyMultiHelloWithErrorByNames func(childComplexity int, reps []*MultiHelloWithErrorByNamesInput) int + FindPlanetRequiresByName func(childComplexity int, name string) int + FindPlanetRequiresNestedByName func(childComplexity int, name string) int + FindWorldByHelloNameAndFoo func(childComplexity int, helloName string, foo string) int + FindWorldNameByName func(childComplexity int, name string) int + FindWorldWithMultipleKeysByBar func(childComplexity int, bar int) int + FindWorldWithMultipleKeysByHelloNameAndFoo func(childComplexity int, helloName string, foo string) int } Hello struct { @@ -99,6 +101,12 @@ type ComplexityRoot struct { Name func(childComplexity int) int } + WorldWithMultipleKeys struct { + Bar func(childComplexity int) int + Foo func(childComplexity int) int + Hello func(childComplexity int) int + } + _Service struct { SDL func(childComplexity int) int } @@ -113,6 +121,8 @@ type EntityResolver interface { FindPlanetRequiresNestedByName(ctx context.Context, name string) (*PlanetRequiresNested, error) FindWorldByHelloNameAndFoo(ctx context.Context, helloName string, foo string) (*World, error) FindWorldNameByName(ctx context.Context, name string) (*WorldName, error) + FindWorldWithMultipleKeysByHelloNameAndFoo(ctx context.Context, helloName string, foo string) (*WorldWithMultipleKeys, error) + FindWorldWithMultipleKeysByBar(ctx context.Context, bar int) (*WorldWithMultipleKeys, error) } type executableSchema struct { @@ -226,6 +236,30 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Entity.FindWorldNameByName(childComplexity, args["name"].(string)), true + case "Entity.findWorldWithMultipleKeysByBar": + if e.complexity.Entity.FindWorldWithMultipleKeysByBar == nil { + break + } + + args, err := ec.field_Entity_findWorldWithMultipleKeysByBar_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Entity.FindWorldWithMultipleKeysByBar(childComplexity, args["bar"].(int)), true + + case "Entity.findWorldWithMultipleKeysByHelloNameAndFoo": + if e.complexity.Entity.FindWorldWithMultipleKeysByHelloNameAndFoo == nil { + break + } + + args, err := ec.field_Entity_findWorldWithMultipleKeysByHelloNameAndFoo_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Entity.FindWorldWithMultipleKeysByHelloNameAndFoo(childComplexity, args["helloName"].(string), args["foo"].(string)), true + case "Hello.name": if e.complexity.Hello.Name == nil { break @@ -350,6 +384,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.WorldName.Name(childComplexity), true + case "WorldWithMultipleKeys.bar": + if e.complexity.WorldWithMultipleKeys.Bar == nil { + break + } + + return e.complexity.WorldWithMultipleKeys.Bar(childComplexity), true + + case "WorldWithMultipleKeys.foo": + if e.complexity.WorldWithMultipleKeys.Foo == nil { + break + } + + return e.complexity.WorldWithMultipleKeys.Foo(childComplexity), true + + case "WorldWithMultipleKeys.hello": + if e.complexity.WorldWithMultipleKeys.Hello == nil { + break + } + + return e.complexity.WorldWithMultipleKeys.Hello(childComplexity), true + case "_Service.sdl": if e.complexity._Service.SDL == nil { break @@ -420,6 +475,12 @@ type World @key(fields: "hello { name } foo ") { hello: Hello } +type WorldWithMultipleKeys @key(fields: "hello { name } foo ") @key(fields: "bar") { + foo: String! + bar: Int! + hello: Hello +} + type WorldName @key(fields: "name") { name: String! } @@ -460,7 +521,7 @@ directive @extends on OBJECT | INTERFACE `, BuiltIn: true}, {Name: "federation/entity.graphql", Input: ` # a union of all types that use the @key directive -union _Entity = Hello | HelloWithErrors | MultiHello | MultiHelloWithError | PlanetRequires | PlanetRequiresNested | World | WorldName +union _Entity = Hello | HelloWithErrors | MultiHello | MultiHelloWithError | PlanetRequires | PlanetRequiresNested | World | WorldName | WorldWithMultipleKeys input MultiHelloByNamesInput { Name: String! } @@ -478,6 +539,8 @@ type Entity { findPlanetRequiresNestedByName(name: String!,): PlanetRequiresNested! findWorldByHelloNameAndFoo(helloName: String!,foo: String!,): World! findWorldNameByName(name: String!,): WorldName! + findWorldWithMultipleKeysByHelloNameAndFoo(helloName: String!,foo: String!,): WorldWithMultipleKeys! + findWorldWithMultipleKeysByBar(bar: Int!,): WorldWithMultipleKeys! } @@ -641,6 +704,45 @@ func (ec *executionContext) field_Entity_findWorldNameByName_args(ctx context.Co return args, nil } +func (ec *executionContext) field_Entity_findWorldWithMultipleKeysByBar_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 int + if tmp, ok := rawArgs["bar"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("bar")) + arg0, err = ec.unmarshalNInt2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["bar"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Entity_findWorldWithMultipleKeysByHelloNameAndFoo_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["helloName"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("helloName")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["helloName"] = arg0 + var arg1 string + if tmp, ok := rawArgs["foo"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("foo")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["foo"] = arg1 + return args, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1087,6 +1189,90 @@ func (ec *executionContext) _Entity_findWorldNameByName(ctx context.Context, fie return ec.marshalNWorldName2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚐWorldName(ctx, field.Selections, res) } +func (ec *executionContext) _Entity_findWorldWithMultipleKeysByHelloNameAndFoo(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Entity", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Entity_findWorldWithMultipleKeysByHelloNameAndFoo_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Entity().FindWorldWithMultipleKeysByHelloNameAndFoo(rctx, args["helloName"].(string), args["foo"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*WorldWithMultipleKeys) + fc.Result = res + return ec.marshalNWorldWithMultipleKeys2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚐWorldWithMultipleKeys(ctx, field.Selections, res) +} + +func (ec *executionContext) _Entity_findWorldWithMultipleKeysByBar(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Entity", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Entity_findWorldWithMultipleKeysByBar_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Entity().FindWorldWithMultipleKeysByBar(rctx, args["bar"].(int)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*WorldWithMultipleKeys) + fc.Result = res + return ec.marshalNWorldWithMultipleKeys2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚐWorldWithMultipleKeys(ctx, field.Selections, res) +} + func (ec *executionContext) _Hello_name(ctx context.Context, field graphql.CollectedField, obj *Hello) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -1757,6 +1943,108 @@ func (ec *executionContext) _WorldName_name(ctx context.Context, field graphql.C return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _WorldWithMultipleKeys_foo(ctx context.Context, field graphql.CollectedField, obj *WorldWithMultipleKeys) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "WorldWithMultipleKeys", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Foo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _WorldWithMultipleKeys_bar(ctx context.Context, field graphql.CollectedField, obj *WorldWithMultipleKeys) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "WorldWithMultipleKeys", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Bar, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + fc.Result = res + return ec.marshalNInt2int(ctx, field.Selections, res) +} + +func (ec *executionContext) _WorldWithMultipleKeys_hello(ctx context.Context, field graphql.CollectedField, obj *WorldWithMultipleKeys) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "WorldWithMultipleKeys", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Hello, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*Hello) + fc.Result = res + return ec.marshalOHello2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚐHello(ctx, field.Selections, res) +} + func (ec *executionContext) __Service_sdl(ctx context.Context, field graphql.CollectedField, obj *fedruntime.Service) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -3021,6 +3309,13 @@ func (ec *executionContext) __Entity(ctx context.Context, sel ast.SelectionSet, return graphql.Null } return ec._WorldName(ctx, sel, obj) + case WorldWithMultipleKeys: + return ec._WorldWithMultipleKeys(ctx, sel, &obj) + case *WorldWithMultipleKeys: + if obj == nil { + return graphql.Null + } + return ec._WorldWithMultipleKeys(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) } @@ -3224,6 +3519,52 @@ func (ec *executionContext) _Entity(ctx context.Context, sel ast.SelectionSet) g return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) } + out.Concurrently(i, func() graphql.Marshaler { + return rrm(innerCtx) + }) + case "findWorldWithMultipleKeysByHelloNameAndFoo": + field := field + + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Entity_findWorldWithMultipleKeysByHelloNameAndFoo(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) + } + + out.Concurrently(i, func() graphql.Marshaler { + return rrm(innerCtx) + }) + case "findWorldWithMultipleKeysByBar": + field := field + + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Entity_findWorldWithMultipleKeysByBar(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) + } + out.Concurrently(i, func() graphql.Marshaler { return rrm(innerCtx) }) @@ -3643,6 +3984,54 @@ func (ec *executionContext) _WorldName(ctx context.Context, sel ast.SelectionSet return out } +var worldWithMultipleKeysImplementors = []string{"WorldWithMultipleKeys", "_Entity"} + +func (ec *executionContext) _WorldWithMultipleKeys(ctx context.Context, sel ast.SelectionSet, obj *WorldWithMultipleKeys) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, worldWithMultipleKeysImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("WorldWithMultipleKeys") + case "foo": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._WorldWithMultipleKeys_foo(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "bar": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._WorldWithMultipleKeys_bar(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "hello": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._WorldWithMultipleKeys_hello(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var _ServiceImplementors = []string{"_Service"} func (ec *executionContext) __Service(ctx context.Context, sel ast.SelectionSet, obj *fedruntime.Service) graphql.Marshaler { @@ -4253,6 +4642,20 @@ func (ec *executionContext) marshalNWorldName2ᚖgithubᚗcomᚋ99designsᚋgqlg return ec._WorldName(ctx, sel, v) } +func (ec *executionContext) marshalNWorldWithMultipleKeys2githubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚐWorldWithMultipleKeys(ctx context.Context, sel ast.SelectionSet, v WorldWithMultipleKeys) graphql.Marshaler { + return ec._WorldWithMultipleKeys(ctx, sel, &v) +} + +func (ec *executionContext) marshalNWorldWithMultipleKeys2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋpluginᚋfederationᚋtestdataᚋentityresolverᚋgeneratedᚐWorldWithMultipleKeys(ctx context.Context, sel ast.SelectionSet, v *WorldWithMultipleKeys) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._WorldWithMultipleKeys(ctx, sel, v) +} + func (ec *executionContext) unmarshalN_Any2map(ctx context.Context, v interface{}) (map[string]interface{}, error) { res, err := graphql.UnmarshalMap(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/plugin/federation/testdata/entityresolver/generated/federation.go b/plugin/federation/testdata/entityresolver/generated/federation.go index 22bbde21a36..0e1e25d935b 100644 --- a/plugin/federation/testdata/entityresolver/generated/federation.go +++ b/plugin/federation/testdata/entityresolver/generated/federation.go @@ -12,6 +12,12 @@ import ( "github.com/99designs/gqlgen/plugin/federation/fedruntime" ) +var ( + ErrUnknownType = errors.New("unknown type") + ErrTypeNotFound = errors.New("type not found") + ErrUnableToResolveEntity = errors.New("unable to resolve Entity") +) + func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { return fedruntime.Service{}, errors.New("federated introspection disabled") @@ -80,135 +86,165 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "Hello": - entity, err := func() (*Hello, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindHelloByName(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForHello(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Hello": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["name"]) if err != nil { - return fmt.Errorf(`resolving Entity "Hello": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "Hello"`) + entity, err := ec.resolvers.Entity().FindHelloByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "Hello": %w`, err) } list[idx[i]] = entity return nil case "HelloWithErrors": - entity, err := func() (*HelloWithErrors, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindHelloWithErrorsByName(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForHelloWithErrors(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "HelloWithErrors": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["name"]) if err != nil { - return fmt.Errorf(`resolving Entity "HelloWithErrors": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "HelloWithErrors"`) + entity, err := ec.resolvers.Entity().FindHelloWithErrorsByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "HelloWithErrors": %w`, err) } list[idx[i]] = entity return nil case "PlanetRequires": - entity, err := func() (*PlanetRequires, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindPlanetRequiresByName(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForPlanetRequires(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "PlanetRequires": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["name"]) if err != nil { - return fmt.Errorf(`resolving Entity "PlanetRequires": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "PlanetRequires"`) + entity, err := ec.resolvers.Entity().FindPlanetRequiresByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "PlanetRequires": %w`, err) } entity.Diameter, err = ec.unmarshalNInt2int(ctx, rep["diameter"]) if err != nil { return err } - list[idx[i]] = entity return nil case "PlanetRequiresNested": - entity, err := func() (*PlanetRequiresNested, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindPlanetRequiresNestedByName(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForPlanetRequiresNested(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "PlanetRequiresNested": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["name"]) if err != nil { - return fmt.Errorf(`resolving Entity "PlanetRequiresNested": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "PlanetRequiresNested"`) + entity, err := ec.resolvers.Entity().FindPlanetRequiresNestedByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "PlanetRequiresNested": %w`, err) } entity.World.Foo, err = ec.unmarshalNString2string(ctx, rep["world"].(map[string]interface{})["foo"]) if err != nil { return err } - list[idx[i]] = entity return nil case "World": - entity, err := func() (*World, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) - if err == nil { - id1, err := ec.unmarshalNString2string(ctx, rep["foo"]) - if err == nil { - return ec.resolvers.Entity().FindWorldByHelloNameAndFoo(ctx, id0, id1) - } - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForWorld(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "World": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) if err != nil { - return fmt.Errorf(`resolving Entity "World": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "World"`) + id1, err := ec.unmarshalNString2string(ctx, rep["foo"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for %s(): %w`, resolverName, err) + } + entity, err := ec.resolvers.Entity().FindWorldByHelloNameAndFoo(ctx, id0, id1) + if err != nil { + return fmt.Errorf(`resolving Entity "World": %w`, err) } list[idx[i]] = entity return nil case "WorldName": - entity, err := func() (*WorldName, error) { - id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindWorldNameByName(ctx, id0) - } - return nil, nil - }() + resolverName, err := ec.entityResolverNameForWorldName(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "WorldName": %w`, err) + } + id0, err := ec.unmarshalNString2string(ctx, rep["name"]) if err != nil { - return fmt.Errorf(`resolving Entity "WorldName": %w`, err) + return fmt.Errorf(`unmarshalling param 0 for %s(): %w`, resolverName, err) } - if entity == nil { - return errors.New(`unable to resolve Entity "WorldName"`) + entity, err := ec.resolvers.Entity().FindWorldNameByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "WorldName": %w`, err) } list[idx[i]] = entity return nil - default: - return errors.New("unknown type: " + typeName) + case "WorldWithMultipleKeys": + resolverName, err := ec.entityResolverNameForWorldWithMultipleKeys(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "WorldWithMultipleKeys": %w`, err) + } + + switch resolverName { + + case "findWorldWithMultipleKeysByHelloNameAndFoo": + id0, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findWorldWithMultipleKeysByHelloNameAndFoo(): %w`, err) + } + id1, err := ec.unmarshalNString2string(ctx, rep["foo"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for findWorldWithMultipleKeysByHelloNameAndFoo(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindWorldWithMultipleKeysByHelloNameAndFoo(ctx, id0, id1) + if err != nil { + return fmt.Errorf(`resolving Entity "WorldWithMultipleKeys": %w`, err) + } + + list[idx[i]] = entity + return nil + case "findWorldWithMultipleKeysByBar": + id0, err := ec.unmarshalNInt2int(ctx, rep["bar"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findWorldWithMultipleKeysByBar(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindWorldWithMultipleKeysByBar(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "WorldWithMultipleKeys": %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) { @@ -322,3 +358,209 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func (ec *executionContext) entityResolverNameForHello(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "name": %w`, err) + } + return "findHelloByName", nil +} + +func (ec *executionContext) entityResolverNameForHelloWithErrors(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "name": %w`, err) + } + return "findHelloWithErrorsByName", nil +} + +func (ec *executionContext) entityResolverNameForMultiHello(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "name": %w`, err) + } + return "findManyMultiHelloByNames", nil +} + +func (ec *executionContext) entityResolverNameForMultiHelloWithError(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "name": %w`, err) + } + return "findManyMultiHelloWithErrorByNames", nil +} + +func (ec *executionContext) entityResolverNameForPlanetRequires(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "name": %w`, err) + } + return "findPlanetRequiresByName", nil +} + +func (ec *executionContext) entityResolverNameForPlanetRequiresNested(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "name": %w`, err) + } + return "findPlanetRequiresNestedByName", nil +} + +func (ec *executionContext) entityResolverNameForWorld(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["hello"]; !ok { + return "", errors.New(`missing parameter "hello"`) + } + + if m, ok = val.(map[string]interface{}); !ok { + return "", errors.New(`parameter "hello" is not a struct`) + } + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "hello.name": %w`, err) + } + m = rep + if val, ok = m["foo"]; !ok { + return "", errors.New(`missing parameter "foo"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "foo": %w`, err) + } + return "findWorldByHelloNameAndFoo", nil +} + +func (ec *executionContext) entityResolverNameForWorldName(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + m = rep + if val, ok = m["name"]; !ok { + return "", errors.New(`missing parameter "name"`) + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + return "", fmt.Errorf(`unmarshalling parameter "name": %w`, err) + } + return "findWorldNameByName", nil +} + +func (ec *executionContext) entityResolverNameForWorldWithMultipleKeys(ctx context.Context, rep map[string]interface{}) (string, error) { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + + for { + m = rep + if val, ok = m["hello"]; !ok { + break + } + + if m, ok = val.(map[string]interface{}); !ok { + break + } + if val, ok = m["name"]; !ok { + break + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + break + } + m = rep + if val, ok = m["foo"]; !ok { + break + } + + if _, err := ec.unmarshalNString2string(ctx, val); err != nil { + break + } + return "findWorldWithMultipleKeysByHelloNameAndFoo", nil + } + for { + m = rep + if val, ok = m["bar"]; !ok { + break + } + + if _, err := ec.unmarshalNInt2int(ctx, val); err != nil { + break + } + return "findWorldWithMultipleKeysByBar", nil + } + return "", fmt.Errorf("%w for WorldWithMultipleKeys", ErrTypeNotFound) +} diff --git a/plugin/federation/testdata/entityresolver/generated/models.go b/plugin/federation/testdata/entityresolver/generated/models.go index b1de872efde..3c26208ccdd 100644 --- a/plugin/federation/testdata/entityresolver/generated/models.go +++ b/plugin/federation/testdata/entityresolver/generated/models.go @@ -64,3 +64,11 @@ type WorldName struct { } func (WorldName) IsEntity() {} + +type WorldWithMultipleKeys struct { + Foo string `json:"foo"` + Bar int `json:"bar"` + Hello *Hello `json:"hello"` +} + +func (WorldWithMultipleKeys) IsEntity() {} diff --git a/plugin/federation/testdata/entityresolver/schema.graphql b/plugin/federation/testdata/entityresolver/schema.graphql index 06b19ecc4a2..bea2ca5f899 100644 --- a/plugin/federation/testdata/entityresolver/schema.graphql +++ b/plugin/federation/testdata/entityresolver/schema.graphql @@ -11,6 +11,12 @@ type World @key(fields: "hello { name } foo ") { hello: Hello } +type WorldWithMultipleKeys @key(fields: "hello { name } foo ") @key(fields: "bar") { + foo: String! + bar: Int! + hello: Hello +} + type WorldName @key(fields: "name") { name: String! }