diff --git a/example/federation/accounts/graph/generated/federation.go b/example/federation/accounts/graph/generated/federation.go index 468fabb319..462941b46d 100644 --- a/example/federation/accounts/graph/generated/federation.go +++ b/example/federation/accounts/graph/generated/federation.go @@ -9,10 +9,14 @@ 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") +) + func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { return fedruntime.Service{}, errors.New("federated introspection disabled") @@ -77,46 +81,48 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "EmailHost": - entity, err := func() (*model.EmailHost, error) { + resolverName, err := entityResolverNameForEmailHost(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "EmailHost": %w`, err) + } + switch resolverName { + + case "findEmailHostByID": id0, err := ec.unmarshalNString2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindEmailHostByID(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findEmailHostByID(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindEmailHostByID(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "EmailHost": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "EmailHost": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "EmailHost"`) + case "User": + resolverName, err := entityResolverNameForUser(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "User": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "User": - entity, err := func() (*model.User, error) { + case "findUserByID": id0, err := ec.unmarshalNID2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindUserByID(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findUserByID(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindUserByID(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "User": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "User": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "User"`) - } - - 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 +188,33 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func entityResolverNameForEmailHost(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["id"]; !ok { + break + } + return "findEmailHostByID", nil + } + return "", fmt.Errorf("%w for EmailHost", ErrTypeNotFound) +} + +func entityResolverNameForUser(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["id"]; !ok { + break + } + return "findUserByID", nil + } + return "", fmt.Errorf("%w for User", ErrTypeNotFound) +} diff --git a/example/federation/products/graph/generated/federation.go b/example/federation/products/graph/generated/federation.go index 5443fd9028..2e31d8ab6a 100644 --- a/example/federation/products/graph/generated/federation.go +++ b/example/federation/products/graph/generated/federation.go @@ -9,10 +9,14 @@ 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") +) + func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { return fedruntime.Service{}, errors.New("federated introspection disabled") @@ -77,59 +81,64 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "Manufacturer": - entity, err := func() (*model.Manufacturer, error) { + resolverName, err := entityResolverNameForManufacturer(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Manufacturer": %w`, err) + } + switch resolverName { + + case "findManufacturerByID": id0, err := ec.unmarshalNString2string(ctx, rep["id"]) - if err == nil { - return ec.resolvers.Entity().FindManufacturerByID(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findManufacturerByID(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindManufacturerByID(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "Manufacturer": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "Manufacturer": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "Manufacturer"`) + case "Product": + resolverName, err := entityResolverNameForProduct(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Product": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "Product": - entity, err := func() (*model.Product, error) { + 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,55 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func entityResolverNameForManufacturer(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["id"]; !ok { + break + } + return "findManufacturerByID", nil + } + return "", fmt.Errorf("%w for Manufacturer", ErrTypeNotFound) +} + +func entityResolverNameForProduct(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + m = rep + if val, ok = m["manufacturer"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["id"]; !ok { + break + } + m = rep + if _, ok = m["id"]; !ok { + break + } + return "findProductByManufacturerIDAndID", nil + } + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["upc"]; !ok { + 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 ab50002cc9..ae0eb99334 100644 --- a/example/federation/reviews/graph/generated/federation.go +++ b/example/federation/reviews/graph/generated/federation.go @@ -9,10 +9,14 @@ 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") +) + func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { return fedruntime.Service{}, errors.New("federated introspection disabled") @@ -77,59 +81,60 @@ 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 := entityResolverNameForProduct(ctx, rep) if err != nil { - return fmt.Errorf(`resolving Entity "Product": %w`, err) - } - if entity == nil { - return errors.New(`unable to resolve Entity "Product"`) + return fmt.Errorf(`finding resolver for Entity "Product": %w`, err) } + switch resolverName { - 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) + case "findProductByManufacturerIDAndID": + id0, err := ec.unmarshalNString2string(ctx, rep["manufacturer"].(map[string]interface{})["id"]) + 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 err != nil { - return fmt.Errorf(`resolving Entity "User": %w`, err) - } - if entity == nil { - return errors.New(`unable to resolve Entity "User"`) + list[idx[i]] = entity + return nil } - - entity.Host.ID, err = ec.unmarshalNString2string(ctx, rep["host"].(map[string]interface{})["id"]) + case "User": + resolverName, err := entityResolverNameForUser(ctx, rep) if err != nil { - return err + return fmt.Errorf(`finding resolver for Entity "User": %w`, err) } + switch resolverName { - entity.Email, err = ec.unmarshalNString2string(ctx, rep["email"]) - if err != nil { - return err - } + case "findUserByID": + id0, err := ec.unmarshalNID2string(ctx, rep["id"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findUserByID(): %w`, err) + } + 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 + 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 +200,44 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func entityResolverNameForProduct(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + m = rep + if val, ok = m["manufacturer"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["id"]; !ok { + break + } + m = rep + if _, ok = m["id"]; !ok { + break + } + return "findProductByManufacturerIDAndID", nil + } + return "", fmt.Errorf("%w for Product", ErrTypeNotFound) +} + +func entityResolverNameForUser(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["id"]; !ok { + break + } + return "findUserByID", nil + } + return "", fmt.Errorf("%w for User", ErrTypeNotFound) +} diff --git a/plugin/federation/federation.go b/plugin/federation/federation.go index 36a24a0253..8e7705dbce 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,23 +196,15 @@ 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 // for _, keyField := range r.KeyFields { if len(keyField.Field) == 0 { - fmt.Println("skipping field " + keyField.Definition.Name + " in " + r.ResolverName + " in " + e.Def.Name) + fmt.Println( + "skipping @key field " + keyField.Definition.Name + " in " + r.ResolverName + " in " + e.Def.Name, + ) continue } cgField := keyField.Field.TypeReference(obj, data.Objects) @@ -229,7 +216,7 @@ func (f *federation) GenerateCode(data *codegen.Data) error { // for _, reqField := range e.Requires { if len(reqField.Field) == 0 { - fmt.Println("skipping requires field " + reqField.Name + " in " + e.Def.Name) + fmt.Println("skipping @requires field " + reqField.Name + " in " + e.Def.Name) continue } cgField := reqField.Field.TypeReference(obj, data.Objects) @@ -247,124 +234,103 @@ 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 { - // TODO: support @key and @extends for interfaces - if dir := schemaType.Directives.ForName("key"); dir != nil { - panic("@key directive is not currently supported for interfaces.") - } - if dir := schemaType.Directives.ForName("extends"); dir != nil { - panic("@extends directive is not currently supported for interfaces.") - } + keys, ok := isFederatedEntity(schemaType) + if !ok { continue } - if schemaType.Kind == ast.Object { - keys := schemaType.Directives.ForNames("key") - if len(keys) == 0 { - continue - } + e := &Entity{ + Name: schemaType.Name, + Def: schemaType, + Resolvers: nil, + Requires: nil, + } - e := &Entity{ - Name: schemaType.Name, - Def: schemaType, - Resolvers: nil, - Requires: nil, + // Let's process custom entity resolver settings. + dir := schemaType.Directives.ForName("entityResolver") + if dir != nil { + if dirArg := dir.Arguments.ForName("multi"); dirArg != nil { + if dirVal, err := dirArg.Value.Value(nil); err == nil { + e.Multi = dirVal.(bool) + } } + } - // Let's process custom entity resolver settings. - dir := schemaType.Directives.ForName("entityResolver") - if dir != nil { - if dirArg := dir.Arguments.ForName("multi"); dirArg != nil { - if dirVal, err := dirArg.Value.Value(nil); err == nil { - e.Multi = dirVal.(bool) - } + // If our schema has a field with a type defined in + // another service, then we need to define an "empty + // extend" of that type in this service, so this service + // knows what the type is like. But the graphql-server + // will never ask us to actually resolve this "empty + // extend", so we don't require a resolver function for + // it. (Well, it will never ask in practice; it's + // unclear whether the spec guarantees this. See + // https://github.com/apollographql/apollo-server/issues/3852 + // ). Example: + // type MyType { + // myvar: TypeDefinedInOtherService + // } + // // Federation needs this type, but + // // it doesn't need a resolver for it! + // extend TypeDefinedInOtherService @key(fields: "id") { + // id: ID @external + // } + if !e.allFieldsAreExternal() { + for _, dir := range keys { + if len(dir.Arguments) != 1 || dir.Arguments[0].Name != "fields" { + panic("Exactly one `fields` argument needed for @key declaration.") } - } + arg := dir.Arguments[0] + keyFieldSet := fieldset.New(arg.Value.Raw, nil) - // If our schema has a field with a type defined in - // another service, then we need to define an "empty - // extend" of that type in this service, so this service - // knows what the type is like. But the graphql-server - // will never ask us to actually resolve this "empty - // extend", so we don't require a resolver function for - // it. (Well, it will never ask in practice; it's - // unclear whether the spec guarantees this. See - // https://github.com/apollographql/apollo-server/issues/3852 - // ). Example: - // type MyType { - // myvar: TypeDefinedInOtherService - // } - // // Federation needs this type, but - // // it doesn't need a resolver for it! - // extend TypeDefinedInOtherService @key(fields: "id") { - // id: ID @external - // } - if !e.allFieldsAreExternal() { - - for _, dir := range keys { - if len(dir.Arguments) != 1 || dir.Arguments[0].Name != "fields" { - panic("Exactly one `fields` argument needed for @key declaration.") - } - arg := dir.Arguments[0] - keyFieldSet := fieldset.New(arg.Value.Raw, nil) + keyFields := make([]*KeyField, len(keyFieldSet)) + resolverFields := []string{} + for i, field := range keyFieldSet { + def := field.FieldDefinition(schemaType, schema) - keyFields := make([]*KeyField, len(keyFieldSet)) - resolverFields := []string{} - for i, field := range keyFieldSet { - def := field.FieldDefinition(schemaType, schema) + if def == nil { + panic(fmt.Sprintf("no field for %v", field)) + } - if def == nil { - panic(fmt.Sprintf("no field for %v", field)) - } + keyFields[i] = &KeyField{Definition: def, Field: field} + resolverFields = append(resolverFields, keyFields[i].Field.ToGo()) + } - keyFields[i] = &KeyField{Definition: def, Field: field} - resolverFields = append(resolverFields, keyFields[i].Field.ToGo()) - } + resolverFieldsToGo := schemaType.Name + "By" + strings.Join(resolverFields, "And") + var resolverName string + if e.Multi { + resolverFieldsToGo += "s" // Pluralize for better API readability + resolverName = fmt.Sprintf("findMany%s", resolverFieldsToGo) + } else { + resolverName = fmt.Sprintf("find%s", resolverFieldsToGo) + } - resolverFieldsToGo := schemaType.Name + "By" + strings.Join(resolverFields, "And") - var resolverName string - if e.Multi { - resolverFieldsToGo += "s" // Pluralize for better API readability - resolverName = fmt.Sprintf("findMany%s", resolverFieldsToGo) - } else { - resolverName = fmt.Sprintf("find%s", resolverFieldsToGo) - } + e.Resolvers = append(e.Resolvers, &EntityResolver{ + ResolverName: resolverName, + KeyFields: keyFields, + InputType: resolverFieldsToGo + "Input", + }) + } - e.Resolvers = append(e.Resolvers, &EntityResolver{ - ResolverName: resolverName, - KeyFields: keyFields, - InputType: resolverFieldsToGo + "Input", + e.Requires = []*Requires{} + for _, f := range schemaType.Fields { + dir := f.Directives.ForName("requires") + if dir == nil { + continue + } + if len(dir.Arguments) != 1 || dir.Arguments[0].Name != "fields" { + panic("Exactly one `fields` argument needed for @requires declaration.") + } + requiresFieldSet := fieldset.New(dir.Arguments[0].Value.Raw, nil) + for _, field := range requiresFieldSet { + e.Requires = append(e.Requires, &Requires{ + Name: field.ToGoPrivate(), + Field: field, }) - - e.Requires = []*Requires{} - for _, f := range schemaType.Fields { - dir := f.Directives.ForName("requires") - if dir == nil { - continue - } - if len(dir.Arguments) != 1 || dir.Arguments[0].Name != "fields" { - panic("Exactly one `fields` argument needed for @requires declaration.") - } - requiresFieldSet := fieldset.New(dir.Arguments[0].Value.Raw, nil) - for _, field := range requiresFieldSet { - e.Requires = append(e.Requires, &Requires{ - Name: field.ToGoPrivate(), - Field: field, - }) - } - } } } - f.Entities = append(f.Entities, e) } + f.Entities = append(f.Entities, e) } // make sure order remains stable across multiple builds @@ -372,3 +338,24 @@ func (f *federation) setEntities(schema *ast.Schema) { return f.Entities[i].Name < f.Entities[j].Name }) } + +func isFederatedEntity(schemaType *ast.Definition) ([]*ast.Directive, bool) { + switch schemaType.Kind { + case ast.Object: + keys := schemaType.Directives.ForNames("key") + if len(keys) > 0 { + return keys, true + } + case ast.Interface: + // TODO: support @key and @extends for interfaces + if dir := schemaType.Directives.ForName("key"); dir != nil { + panic("@key directive is not currently supported for interfaces.") + } + if dir := schemaType.Directives.ForName("extends"); dir != nil { + panic("@extends directive is not currently supported for interfaces.") + } + default: + // ignore + } + return nil, false +} diff --git a/plugin/federation/federation.gotpl b/plugin/federation/federation.gotpl index c73ab00557..fdbbf0753f 100644 --- a/plugin/federation/federation.gotpl +++ b/plugin/federation/federation.gotpl @@ -6,7 +6,10 @@ {{ reserveImport "github.com/99designs/gqlgen/plugin/federation/fedruntime" }} -{{ .Imports }} +var ( + ErrUnknownType = errors.New("unknown type") + ErrTypeNotFound = errors.New("type not found") +) func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { @@ -80,46 +83,40 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati }() switch typeName { - {{ 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 -}} - 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 -}} - } - {{- 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{})["`}}"]) + {{ range $_, $entity := .Entities }} + {{- if and .Resolvers (not .Multi) -}} + case "{{.Def.Name}}": + resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, rep) if err != nil { - return err + return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err) + } + 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 { + return fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err) + } + {{- end}} + 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 }} - - list[idx[i]] = entity - return nil - {{ end }} - {{ end }} - {{- end }} - default: - return errors.New("unknown type: "+typeName) + {{- end }} } + return fmt.Errorf("%w: %s", ErrUnknownType, typeName) } resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { @@ -132,49 +129,47 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati }() switch typeName { - {{ range $_, $entity := .Entities }} - {{ if .Resolvers -}} - {{ if .Multi -}} + {{ range $_, $entity := .Entities }} + {{ if and .Resolvers .Multi -}} case "{{.Def.Name}}": - {{range $i, $_ := .Resolvers -}} - _reps := make([]*{{.InputType}}, len(reps)) + {{range $i, $_ := .Resolvers -}} + _reps := make([]*{{.InputType}}, len(reps)) - for i, rep := range reps { - {{ range $i, $keyField := .KeyFields -}} - id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) - if err != nil { - return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{.Definition.Name}}")) - } - {{end}} + for i, rep := range reps { + {{ range $i, $keyField := .KeyFields -}} + id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) + if err != nil { + return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{.Definition.Name}}")) + } + {{end}} - _reps[i] = &{{.InputType}} { - {{ range $i, $keyField := .KeyFields -}} - {{$keyField.Field.ToGo}}: id{{$i}}, - {{end}} + _reps[i] = &{{.InputType}} { + {{ range $i, $keyField := .KeyFields -}} + {{$keyField.Field.ToGo}}: id{{$i}}, + {{end}} + } } - } - entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, _reps) - if err != nil { - return err - } + entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, _reps) + if err != nil { + return err + } - for i, entity := range entities { - {{- range $entity.Requires -}} - {{- range .Fields -}} - entity.{{.NameGo}}, err = ec.{{.TypeReference.UnmarshalFunc}}(ctx, reps[i]["{{.Name}}"]) - if err != nil { - return err - } + for i, entity := range entities { + {{- range $entity.Requires -}} + {{- range .Fields -}} + entity.{{.NameGo}}, err = ec.{{.TypeReference.UnmarshalFunc}}(ctx, reps[i]["{{.Name}}"]) + if err != nil { + return err + } + {{- end -}} {{- end -}} - {{- end -}} - list[idx[i]] = entity - } - return nil + list[idx[i]] = entity + } + return nil + {{ end }} {{ end }} - {{ end }} - {{ end }} - {{- end }} + {{- end }} default: return errors.New("unknown type: "+typeName) } @@ -227,4 +222,41 @@ 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 entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep map[string]interface{}) (string, error) { + {{- range .Resolvers }} + for { + var ( + m map[string]interface{} + {{- if gt (len .KeyFields) 1 }} + val interface{} + {{- end }} + ok bool + ) + {{- range $_, $keyField := .KeyFields }} + m = rep + {{- range $i, $field := .Field }} + if {{ if (ne $i $keyField.Field.LastIndex ) -}}val{{- else -}}_{{- end -}}, ok = m["{{.}}"]; !ok { + break + } + {{- if (ne $i $keyField.Field.LastIndex ) }} + if m, ok = val.(map[string]interface{}); !ok { + break + } + {{- end}} + {{- end}} + {{- end }} + return "{{.ResolverName}}", nil + } + {{- end }} + return "", fmt.Errorf("%w for {{$entity.Name}}", ErrTypeNotFound) + } + {{- end }} +{{- end }} + {{end}} diff --git a/plugin/federation/federation_entityresolver_test.go b/plugin/federation/federation_entityresolver_test.go index 23959d0883..00c76d5cd8 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 a7157ed673..35616cbea3 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 6cf0fe8cd3..5fdcd29e5b 100644 --- a/plugin/federation/testdata/allthethings/generated/federation.go +++ b/plugin/federation/testdata/allthethings/generated/federation.go @@ -10,7 +10,11 @@ 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") ) func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { @@ -77,138 +81,148 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "ExternalExtension": - entity, err := func() (*model.ExternalExtension, error) { + resolverName, err := entityResolverNameForExternalExtension(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "ExternalExtension": %w`, err) + } + switch resolverName { + + case "findExternalExtensionByUpc": id0, err := ec.unmarshalNString2string(ctx, rep["upc"]) - if err == nil { - return ec.resolvers.Entity().FindExternalExtensionByUpc(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findExternalExtensionByUpc(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindExternalExtensionByUpc(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "ExternalExtension": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "ExternalExtension": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "ExternalExtension"`) + case "Hello": + resolverName, err := entityResolverNameForHello(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Hello": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "Hello": - entity, err := func() (*model.Hello, error) { + case "findHelloByName": id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindHelloByName(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findHelloByName(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindHelloByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "Hello": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "Hello": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "Hello"`) + case "NestedKey": + resolverName, err := entityResolverNameForNestedKey(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "NestedKey": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "NestedKey": - entity, err := func() (*model.NestedKey, error) { + case "findNestedKeyByIDAndHelloName": 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) - } + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findNestedKeyByIDAndHelloName(): %w`, err) + } + id1, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for findNestedKeyByIDAndHelloName(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindNestedKeyByIDAndHelloName(ctx, id0, id1) + if err != nil { + return fmt.Errorf(`resolving Entity "NestedKey": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "NestedKey": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "NestedKey"`) + case "VeryNestedKey": + resolverName, err := entityResolverNameForVeryNestedKey(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "VeryNestedKey": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "VeryNestedKey": - entity, err := func() (*model.VeryNestedKey, error) { + case "findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo": 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) - } - } - } - } + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo(): %w`, err) + } + id1, err := ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["name"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo(): %w`, err) + } + id2, err := ec.unmarshalNString2string(ctx, rep["world"].(map[string]interface{})["foo"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 2 for findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo(): %w`, err) + } + id3, err := ec.unmarshalNInt2int(ctx, rep["world"].(map[string]interface{})["bar"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 3 for findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo(): %w`, 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 findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo(ctx, id0, id1, id2, id3, id4) + if err != nil { + return fmt.Errorf(`resolving Entity "VeryNestedKey": %w`, err) } - return nil, nil - }() - - if err != nil { - return fmt.Errorf(`resolving Entity "VeryNestedKey": %w`, err) - } - if entity == nil { - return errors.New(`unable to resolve Entity "VeryNestedKey"`) - } - entity.ID, err = ec.unmarshalNString2string(ctx, rep["id"]) - if err != nil { - return 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 } - - entity.Hello.Secondary, err = ec.unmarshalNString2string(ctx, rep["hello"].(map[string]interface{})["secondary"]) + case "World": + resolverName, err := entityResolverNameForWorld(ctx, rep) if err != nil { - return err + return fmt.Errorf(`finding resolver for Entity "World": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "World": - entity, err := func() (*model.World, error) { + 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 +288,147 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func entityResolverNameForExternalExtension(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["upc"]; !ok { + break + } + return "findExternalExtensionByUpc", nil + } + return "", fmt.Errorf("%w for ExternalExtension", ErrTypeNotFound) +} + +func entityResolverNameForHello(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["name"]; !ok { + break + } + return "findHelloByName", nil + } + return "", fmt.Errorf("%w for Hello", ErrTypeNotFound) +} + +func entityResolverNameForNestedKey(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + m = rep + if _, ok = m["id"]; !ok { + break + } + m = rep + if val, ok = m["hello"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["name"]; !ok { + break + } + return "findNestedKeyByIDAndHelloName", nil + } + return "", fmt.Errorf("%w for NestedKey", ErrTypeNotFound) +} + +func entityResolverNameForVeryNestedKey(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + m = rep + if _, ok = m["id"]; !ok { + break + } + m = rep + if val, ok = m["hello"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["name"]; !ok { + break + } + m = rep + if val, ok = m["world"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["foo"]; !ok { + break + } + m = rep + if val, ok = m["world"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["bar"]; !ok { + break + } + m = rep + if val, ok = m["more"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if val, ok = m["world"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["foo"]; !ok { + break + } + return "findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo", nil + } + return "", fmt.Errorf("%w for VeryNestedKey", ErrTypeNotFound) +} + +func entityResolverNameForWorld(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["foo"]; !ok { + break + } + return "findWorldByFoo", nil + } + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["bar"]; !ok { + 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 6c0f238bde..0a2ff3fcff 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 c15b788185..23ba9f3fb7 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 22bbde21a3..05d237152e 100644 --- a/plugin/federation/testdata/entityresolver/generated/federation.go +++ b/plugin/federation/testdata/entityresolver/generated/federation.go @@ -12,6 +12,11 @@ import ( "github.com/99designs/gqlgen/plugin/federation/fedruntime" ) +var ( + ErrUnknownType = errors.New("unknown type") + ErrTypeNotFound = errors.New("type not found") +) + func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { if ec.DisableIntrospection { return fedruntime.Service{}, errors.New("federated introspection disabled") @@ -80,135 +85,176 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati switch typeName { case "Hello": - entity, err := func() (*Hello, error) { + resolverName, err := entityResolverNameForHello(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "Hello": %w`, err) + } + switch resolverName { + + case "findHelloByName": id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindHelloByName(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findHelloByName(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindHelloByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "Hello": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "Hello": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "Hello"`) + case "HelloWithErrors": + resolverName, err := entityResolverNameForHelloWithErrors(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "HelloWithErrors": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "HelloWithErrors": - entity, err := func() (*HelloWithErrors, error) { + case "findHelloWithErrorsByName": id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindHelloWithErrorsByName(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findHelloWithErrorsByName(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindHelloWithErrorsByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "HelloWithErrors": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "HelloWithErrors": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "HelloWithErrors"`) + case "PlanetRequires": + resolverName, err := entityResolverNameForPlanetRequires(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "PlanetRequires": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "PlanetRequires": - entity, err := func() (*PlanetRequires, error) { + case "findPlanetRequiresByName": id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindPlanetRequiresByName(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findPlanetRequiresByName(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindPlanetRequiresByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "PlanetRequires": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "PlanetRequires": %w`, err) - } - if entity == nil { - return errors.New(`unable to resolve Entity "PlanetRequires"`) + entity.Diameter, err = ec.unmarshalNInt2int(ctx, rep["diameter"]) + if err != nil { + return err + } + list[idx[i]] = entity + return nil } - - entity.Diameter, err = ec.unmarshalNInt2int(ctx, rep["diameter"]) + case "PlanetRequiresNested": + resolverName, err := entityResolverNameForPlanetRequiresNested(ctx, rep) if err != nil { - return err + return fmt.Errorf(`finding resolver for Entity "PlanetRequiresNested": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "PlanetRequiresNested": - entity, err := func() (*PlanetRequiresNested, error) { + case "findPlanetRequiresNestedByName": id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindPlanetRequiresNestedByName(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findPlanetRequiresNestedByName(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindPlanetRequiresNestedByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "PlanetRequiresNested": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "PlanetRequiresNested": %w`, err) - } - if entity == nil { - return errors.New(`unable to resolve Entity "PlanetRequiresNested"`) + entity.World.Foo, err = ec.unmarshalNString2string(ctx, rep["world"].(map[string]interface{})["foo"]) + if err != nil { + return err + } + list[idx[i]] = entity + return nil } - - entity.World.Foo, err = ec.unmarshalNString2string(ctx, rep["world"].(map[string]interface{})["foo"]) + case "World": + resolverName, err := entityResolverNameForWorld(ctx, rep) if err != nil { - return err + return fmt.Errorf(`finding resolver for Entity "World": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "World": - entity, err := func() (*World, error) { + case "findWorldByHelloNameAndFoo": 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) - } + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findWorldByHelloNameAndFoo(): %w`, err) + } + id1, err := ec.unmarshalNString2string(ctx, rep["foo"]) + if err != nil { + return fmt.Errorf(`unmarshalling param 1 for findWorldByHelloNameAndFoo(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindWorldByHelloNameAndFoo(ctx, id0, id1) + if err != nil { + return fmt.Errorf(`resolving Entity "World": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "World": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "World"`) + case "WorldName": + resolverName, err := entityResolverNameForWorldName(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "WorldName": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil - - case "WorldName": - entity, err := func() (*WorldName, error) { + case "findWorldNameByName": id0, err := ec.unmarshalNString2string(ctx, rep["name"]) - if err == nil { - return ec.resolvers.Entity().FindWorldNameByName(ctx, id0) + if err != nil { + return fmt.Errorf(`unmarshalling param 0 for findWorldNameByName(): %w`, err) + } + entity, err := ec.resolvers.Entity().FindWorldNameByName(ctx, id0) + if err != nil { + return fmt.Errorf(`resolving Entity "WorldName": %w`, err) } - return nil, nil - }() - if err != nil { - return fmt.Errorf(`resolving Entity "WorldName": %w`, err) + list[idx[i]] = entity + return nil } - if entity == nil { - return errors.New(`unable to resolve Entity "WorldName"`) + case "WorldWithMultipleKeys": + resolverName, err := entityResolverNameForWorldWithMultipleKeys(ctx, rep) + if err != nil { + return fmt.Errorf(`finding resolver for Entity "WorldWithMultipleKeys": %w`, err) } + switch resolverName { - list[idx[i]] = entity - return nil + 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 + } - 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) { @@ -322,3 +368,171 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return list } } + +func entityResolverNameForHello(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["name"]; !ok { + break + } + return "findHelloByName", nil + } + return "", fmt.Errorf("%w for Hello", ErrTypeNotFound) +} + +func entityResolverNameForHelloWithErrors(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["name"]; !ok { + break + } + return "findHelloWithErrorsByName", nil + } + return "", fmt.Errorf("%w for HelloWithErrors", ErrTypeNotFound) +} + +func entityResolverNameForMultiHello(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["name"]; !ok { + break + } + return "findManyMultiHelloByNames", nil + } + return "", fmt.Errorf("%w for MultiHello", ErrTypeNotFound) +} + +func entityResolverNameForMultiHelloWithError(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["name"]; !ok { + break + } + return "findManyMultiHelloWithErrorByNames", nil + } + return "", fmt.Errorf("%w for MultiHelloWithError", ErrTypeNotFound) +} + +func entityResolverNameForPlanetRequires(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["name"]; !ok { + break + } + return "findPlanetRequiresByName", nil + } + return "", fmt.Errorf("%w for PlanetRequires", ErrTypeNotFound) +} + +func entityResolverNameForPlanetRequiresNested(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["name"]; !ok { + break + } + return "findPlanetRequiresNestedByName", nil + } + return "", fmt.Errorf("%w for PlanetRequiresNested", ErrTypeNotFound) +} + +func entityResolverNameForWorld(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + m = rep + if val, ok = m["hello"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["name"]; !ok { + break + } + m = rep + if _, ok = m["foo"]; !ok { + break + } + return "findWorldByHelloNameAndFoo", nil + } + return "", fmt.Errorf("%w for World", ErrTypeNotFound) +} + +func entityResolverNameForWorldName(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["name"]; !ok { + break + } + return "findWorldNameByName", nil + } + return "", fmt.Errorf("%w for WorldName", ErrTypeNotFound) +} + +func entityResolverNameForWorldWithMultipleKeys(ctx context.Context, rep map[string]interface{}) (string, error) { + for { + var ( + m map[string]interface{} + val interface{} + ok bool + ) + m = rep + if val, ok = m["hello"]; !ok { + break + } + if m, ok = val.(map[string]interface{}); !ok { + break + } + if _, ok = m["name"]; !ok { + break + } + m = rep + if _, ok = m["foo"]; !ok { + break + } + return "findWorldWithMultipleKeysByHelloNameAndFoo", nil + } + for { + var ( + m map[string]interface{} + ok bool + ) + m = rep + if _, ok = m["bar"]; !ok { + 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 b1de872efd..3c26208ccd 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 06b19ecc4a..bea2ca5f89 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! }