{{ reserveImport "context" }} {{ reserveImport "errors" }} {{ reserveImport "fmt" }} {{ reserveImport "strings" }} {{ reserveImport "sync" }} {{ reserveImport "github.com/99designs/gqlgen/plugin/federation/fedruntime" }} {{ $options := .PackageOptions }} {{ $usePointers := .UsePointers }} 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") } var sdl []string for _, src := range sources { if src.BuiltIn { continue } sdl = append(sdl, src.Input) } return fedruntime.Service{ SDL: strings.Join(sdl, "\n"), }, nil } {{if .Entities}} func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity { list := make([]fedruntime.Entity, len(representations)) repsMap := map[string]struct { i []int r []map[string]interface{} }{} // We group entities by typename so that we can parallelize their resolution. // This is particularly helpful when there are entity groups in multi mode. buildRepresentationGroups := func(reps []map[string]interface{}) { for i, rep := range reps { typeName, ok := rep["__typename"].(string) if !ok { // If there is no __typename, we just skip the representation; // we just won't be resolving these unknown types. ec.Error(ctx, errors.New("__typename must be an existing string")) continue } _r := repsMap[typeName] _r.i = append(_r.i, i) _r.r = append(_r.r, rep) repsMap[typeName] = _r } } isMulti := func(typeName string) bool { switch typeName { {{- range .Entities -}} {{- if .Resolvers -}} {{- if .Multi -}} case "{{.Def.Name}}": return true {{ end }} {{- end -}} {{- end -}} default: return false } } resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (err error) { // we need to do our own panic handling, because we may be called in a // goroutine, where the usual panic handling can't catch us defer func () { if r := recover(); r != nil { err = ec.Recover(ctx, r) } }() switch typeName { {{ range $_, $entity := .Entities }} {{- if and .Resolvers (not .Multi) -}} case "{{.Def.Name}}": resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, rep) if err != nil { 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) } {{ if and (index $options "explicit_requires") $entity.Requires }} err = ec.Populate{{$entity.Def.Name}}Requires(ctx, {{- if (not $usePointers) -}}&{{- end -}}entity, rep) if err != nil { return fmt.Errorf(`populating requires for Entity "{{$entity.Def.Name}}": %w`, err) } {{- else }} {{ range $entity.Requires }} entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) if err != nil { return err } {{- end }} {{- end }} list[idx[i]] = entity return nil {{- end }} } {{ end }} {{- end }} } return fmt.Errorf("%w: %s", ErrUnknownType, typeName) } resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { // we need to do our own panic handling, because we may be called in a // goroutine, where the usual panic handling can't catch us defer func () { if r := recover(); r != nil { err = ec.Recover(ctx, r) } }() switch typeName { {{ range $_, $entity := .Entities }} {{ if and .Resolvers .Multi -}} case "{{.Def.Name}}": resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, reps[0]) if err != nil { return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err) } switch resolverName { {{ range $i, $resolver := .Resolvers }} case "{{.ResolverName}}": _reps := make([]*{{.LookupInputType}}, 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}} _reps[i] = &{{.LookupInputType}} { {{ range $i, $keyField := .KeyFields -}} {{$keyField.Field.ToGo}}: id{{$i}}, {{end}} } } entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, _reps) if err != nil { return err } for i, entity := range entities { {{- range $entity.Requires }} entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i]["{{.Field.Join `"].(map[string]interface{})["`}}"]) if err != nil { return err } {{- end}} list[idx[i]] = entity } return nil {{ end }} default: return fmt.Errorf("unknown resolver: %s", resolverName) } {{ end }} {{- end }} default: return errors.New("unknown type: "+typeName) } } resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) { if isMulti(typeName) { err := resolveManyEntities(ctx, typeName, reps, idx) if err != nil { ec.Error(ctx, err) } } else { // if there are multiple entities to resolve, parallelize (similar to // graphql.FieldSet.Dispatch) var e sync.WaitGroup e.Add(len(reps)) for i, rep := range reps { i, rep := i, rep go func(i int, rep map[string]interface{}) { err := resolveEntity(ctx, typeName, rep, idx, i) if err != nil { ec.Error(ctx, err) } e.Done() }(i, rep) } e.Wait() } } buildRepresentationGroups(representations) switch len(repsMap) { case 0: return list case 1: for typeName, reps := range repsMap { resolveEntityGroup(typeName, reps.r, reps.i) } return list default: var g sync.WaitGroup g.Add(len(repsMap)) for typeName, reps := range repsMap { go func(typeName string, reps []map[string]interface{}, idx []int) { resolveEntityGroup(typeName, reps, idx) g.Done() }(typeName, reps.r, reps.i) } g.Wait() return list } } {{- /* 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{} val interface{} ok bool ) _ = val {{- 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}}