...

Text file src/github.com/99designs/gqlgen/plugin/federation/federation.gotpl

Documentation: github.com/99designs/gqlgen/plugin/federation

     1{{ reserveImport "context"  }}
     2{{ reserveImport "errors"  }}
     3{{ reserveImport "fmt"  }}
     4{{ reserveImport "strings"  }}
     5{{ reserveImport "sync"  }}
     6
     7{{ reserveImport "github.com/99designs/gqlgen/plugin/federation/fedruntime" }}
     8{{ $options := .PackageOptions }}
     9{{ $usePointers := .UsePointers }}
    10
    11var (
    12	ErrUnknownType = errors.New("unknown type")
    13	ErrTypeNotFound = errors.New("type not found")
    14)
    15
    16func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) {
    17	if ec.DisableIntrospection {
    18		return fedruntime.Service{}, errors.New("federated introspection disabled")
    19	}
    20
    21	var sdl []string
    22
    23	for _, src := range sources {
    24		if src.BuiltIn {
    25			continue
    26		}
    27		sdl = append(sdl, src.Input)
    28	}
    29
    30	return fedruntime.Service{
    31		SDL: strings.Join(sdl, "\n"),
    32	}, nil
    33}
    34
    35{{if .Entities}}
    36func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity {
    37	list := make([]fedruntime.Entity, len(representations))
    38
    39	repsMap := map[string]struct {
    40		i []int
    41		r []map[string]interface{}
    42	}{}
    43
    44	// We group entities by typename so that we can parallelize their resolution.
    45	// This is particularly helpful when there are entity groups in multi mode.
    46	buildRepresentationGroups := func(reps []map[string]interface{}) {
    47		for i, rep := range reps {
    48			typeName, ok := rep["__typename"].(string)
    49			if !ok {
    50				// If there is no __typename, we just skip the representation;
    51				// we just won't be resolving these unknown types.
    52				ec.Error(ctx, errors.New("__typename must be an existing string"))
    53				continue
    54			}
    55
    56			_r := repsMap[typeName]
    57			_r.i = append(_r.i, i)
    58			_r.r = append(_r.r, rep)
    59			repsMap[typeName] = _r
    60		}
    61	}
    62
    63	isMulti := func(typeName string) bool {
    64		switch typeName {
    65		{{- range .Entities -}}
    66			{{- if .Resolvers -}}
    67				{{- if .Multi -}}
    68			case "{{.Def.Name}}":
    69				return true
    70				{{ end }}
    71			{{- end -}}
    72		{{- end -}}
    73		default:
    74			return false
    75		}
    76	}
    77
    78	resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (err error) {
    79		// we need to do our own panic handling, because we may be called in a
    80		// goroutine, where the usual panic handling can't catch us
    81		defer func () {
    82			if r := recover(); r != nil {
    83				err = ec.Recover(ctx, r)
    84			}
    85		}()
    86
    87		switch typeName {
    88			{{ range $_, $entity := .Entities }}
    89				{{- if and .Resolvers (not .Multi) -}}
    90				case "{{.Def.Name}}":
    91					resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, rep)
    92					if err != nil {
    93						return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err)
    94					}
    95					switch resolverName {
    96					{{ range $i, $resolver := .Resolvers }}
    97					case "{{.ResolverName}}":
    98						{{- range $j, $keyField := .KeyFields }}
    99							id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
   100							if err != nil {
   101								return fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err)
   102							}
   103						{{- end}}
   104						entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, {{- range $j, $_ := .KeyFields -}} id{{$j}}, {{end}})
   105						if err != nil {
   106							return fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err)
   107						}
   108						{{ if and (index $options "explicit_requires") $entity.Requires }}
   109							err = ec.Populate{{$entity.Def.Name}}Requires(ctx, {{- if (not $usePointers) -}}&{{- end -}}entity, rep)
   110							if err != nil {
   111								return fmt.Errorf(`populating requires for Entity "{{$entity.Def.Name}}": %w`, err)
   112							}
   113						{{- else }}
   114							{{ range $entity.Requires }}
   115								entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
   116								if err != nil {
   117									return err
   118								}
   119							{{- end }}
   120						{{- end }}
   121						list[idx[i]] = entity
   122						return nil
   123					{{- end }}
   124					}
   125				{{ end }}
   126			{{- end }}
   127		}
   128		return fmt.Errorf("%w: %s", ErrUnknownType, typeName)
   129	}
   130
   131	resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) {
   132		// we need to do our own panic handling, because we may be called in a
   133		// goroutine, where the usual panic handling can't catch us
   134		defer func () {
   135			if r := recover(); r != nil {
   136				err = ec.Recover(ctx, r)
   137			}
   138		}()
   139
   140		switch typeName {
   141			{{ range $_, $entity := .Entities }}
   142				{{ if and .Resolvers .Multi -}}
   143				case "{{.Def.Name}}":
   144					resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, reps[0])
   145					if err != nil {
   146						return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err)
   147					}
   148					switch resolverName {
   149					{{ range $i, $resolver := .Resolvers }}
   150					case "{{.ResolverName}}":
   151						_reps := make([]*{{.LookupInputType}}, len(reps))
   152
   153						for i, rep := range reps {
   154							{{ range $i, $keyField := .KeyFields -}}
   155								id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
   156								if err != nil {
   157									return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{.Definition.Name}}"))
   158								}
   159							{{end}}
   160
   161							_reps[i] = &{{.LookupInputType}} {
   162							{{ range $i, $keyField := .KeyFields -}}
   163								{{$keyField.Field.ToGo}}: id{{$i}},
   164							{{end}}
   165							}
   166						}
   167
   168						entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, _reps)
   169						if err != nil {
   170							return err
   171						}
   172
   173						for i, entity := range entities {
   174							{{- range $entity.Requires }}
   175									entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i]["{{.Field.Join `"].(map[string]interface{})["`}}"])
   176									if err != nil {
   177										return err
   178									}
   179							{{- end}}
   180							list[idx[i]] = entity
   181						}
   182						return nil
   183					{{ end }}
   184					default:
   185                    	return fmt.Errorf("unknown resolver: %s", resolverName)
   186					}
   187				{{ end }}
   188			{{- end }}
   189		default:
   190			return errors.New("unknown type: "+typeName)
   191		}
   192	}
   193
   194	resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) {
   195		if isMulti(typeName) {
   196			err := resolveManyEntities(ctx, typeName, reps, idx)
   197			if err != nil {
   198				ec.Error(ctx, err)
   199			}
   200		} else {
   201			// if there are multiple entities to resolve, parallelize (similar to
   202			// graphql.FieldSet.Dispatch)
   203			var e sync.WaitGroup
   204			e.Add(len(reps))
   205			for i, rep := range reps {
   206				i, rep := i, rep
   207				go func(i int, rep map[string]interface{}) {
   208					err := resolveEntity(ctx, typeName, rep, idx, i)
   209					if err != nil {
   210						ec.Error(ctx, err)
   211					}
   212					e.Done()
   213				}(i, rep)
   214			}
   215			e.Wait()
   216		}
   217	}
   218	buildRepresentationGroups(representations)
   219
   220	switch len(repsMap) {
   221	case 0:
   222		return list
   223	case 1:
   224		for typeName, reps := range repsMap {
   225			resolveEntityGroup(typeName, reps.r, reps.i)
   226		}
   227		return list
   228	default:
   229		var g sync.WaitGroup
   230		g.Add(len(repsMap))
   231		for typeName, reps := range repsMap {
   232			go func(typeName string, reps []map[string]interface{}, idx []int) {
   233				resolveEntityGroup(typeName, reps, idx)
   234				g.Done()
   235			}(typeName, reps.r, reps.i)
   236		}
   237		g.Wait()
   238		return list
   239	}
   240}
   241
   242{{- /* Make sure the required fields are in the given entity representation and return the name of the proper resolver. */ -}}
   243
   244{{ range $_, $entity := .Entities }}
   245	{{- if .Resolvers }}
   246
   247		func entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep map[string]interface{}) (string, error) {
   248			{{- range .Resolvers }}
   249				for {
   250					var (
   251						m    map[string]interface{}
   252						val interface{}
   253						ok bool
   254					)
   255					_ = val
   256					{{- range $_, $keyField := .KeyFields }}
   257						m = rep
   258						{{- range $i, $field := .Field }}
   259							if {{ if (ne $i $keyField.Field.LastIndex ) -}}val{{- else -}}_{{- end -}}, ok = m["{{.}}"]; !ok {
   260								break
   261							}
   262							{{- if (ne $i $keyField.Field.LastIndex ) }}
   263								if m, ok = val.(map[string]interface{}); !ok {
   264									break
   265								}
   266							{{- end}}
   267						{{- end}}
   268					{{- end }}
   269					return "{{.ResolverName}}", nil
   270				}
   271			{{- end }}
   272			return "", fmt.Errorf("%w for {{$entity.Name}}", ErrTypeNotFound)
   273		}
   274	{{- end }}
   275{{- end }}
   276
   277{{end}}

View as plain text