github.com/operandinc/gqlgen@v0.16.1/plugin/federation/federation.gotpl (about)

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