diff --git a/README.md b/README.md index eaf37b10..9d65f764 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ When using `UseFieldResolvers` schema option, a struct field will be used *only* The method has up to two arguments: -- Optional `context.Context` argument. +- Optional `context.Context` argument. If the graphql query had nested subfields, then use the `SelctedFieldsFromContext(ctx context.Context)` getter method - Mandatory `*struct { ... }` argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be [exported](https://golang.org/ref/spec#Exported_identifiers) and have to match the names of the GraphQL arguments in a non-case-sensitive way. The method has up to two results: diff --git a/graphql.go b/graphql.go index 0a661791..bb19c8f5 100644 --- a/graphql.go +++ b/graphql.go @@ -16,6 +16,7 @@ import ( "github.com/graph-gophers/graphql-go/internal/validation" "github.com/graph-gophers/graphql-go/introspection" "github.com/graph-gophers/graphql-go/log" + "github.com/graph-gophers/graphql-go/selection" "github.com/graph-gophers/graphql-go/trace" ) @@ -144,6 +145,12 @@ type Response struct { Extensions map[string]interface{} `json:"extensions,omitempty"` } +// SelectedFieldsFromContext retrieves the selected fields passed via the context during the request +// execution +func SelectedFieldsFromContext(ctx context.Context) []*selection.SelectedField { + return exec.SelectedFieldsFromContext(ctx) +} + // Validate validates the given query with the schema. func (s *Schema) Validate(queryString string) []*errors.QueryError { doc, qErr := query.Parse(queryString) @@ -184,11 +191,11 @@ func (s *Schema) exec(ctx context.Context, queryString string, operationName str // Subscriptions are not valid in Exec. Use schema.Subscribe() instead. if op.Type == query.Subscription { - return &Response{Errors: []*errors.QueryError{&errors.QueryError{ Message: "graphql-ws protocol header is missing" }}} + return &Response{Errors: []*errors.QueryError{&errors.QueryError{Message: "graphql-ws protocol header is missing"}}} } if op.Type == query.Mutation { if _, ok := s.schema.EntryPoints["mutation"]; !ok { - return &Response{Errors: []*errors.QueryError{{ Message: "no mutations are offered by the schema" }}} + return &Response{Errors: []*errors.QueryError{{Message: "no mutations are offered by the schema"}}} } } diff --git a/internal/exec/exec.go b/internal/exec/exec.go index dc0aa72f..abbd1249 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -15,9 +15,16 @@ import ( "github.com/graph-gophers/graphql-go/internal/query" "github.com/graph-gophers/graphql-go/internal/schema" "github.com/graph-gophers/graphql-go/log" + "github.com/graph-gophers/graphql-go/selection" "github.com/graph-gophers/graphql-go/trace" ) +type ctxKey string + +const ( + selectedFieldsKey ctxKey = "selectedFields" +) + type Request struct { selected.Request Limiter chan struct{} @@ -160,6 +167,32 @@ func typeOf(tf *selected.TypenameField, resolver reflect.Value) string { return "" } +func selectionToSelectedFields(internalSelection []selected.Selection) []*selection.SelectedField { + fieldSelection := []*selection.SelectedField{} + for _, element := range internalSelection { + if field, ok := element.(*selected.SchemaField); ok { + nestedSelections := selectionToSelectedFields(field.Sels) + fieldSelection = append(fieldSelection, &selection.SelectedField{ + Name: field.Name, + SelectedFields: nestedSelections, + }) + } + } + return fieldSelection +} + +// SelectedFieldsFromContext exposes the fields selected in the GraphQL request +// using the public-facing selection.SelectedField struct +func SelectedFieldsFromContext(ctx context.Context) []*selection.SelectedField { + selection := ctx.Value(selectedFieldsKey).([]selected.Selection) + selectedFields := selectionToSelectedFields(selection) + return selectedFields +} + +func contextWithSelectedFields(parentContext context.Context, selection []selected.Selection) context.Context { + return context.WithValue(parentContext, selectedFieldsKey, selection) +} + func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f *fieldToExec, path *pathSegment, applyLimiter bool) { if applyLimiter { r.Limiter <- struct{}{} @@ -195,6 +228,9 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if f.field.UseMethodResolver() { var in []reflect.Value if f.field.HasContext { + if len(f.sels) != 0 { + traceCtx = contextWithSelectedFields(traceCtx, f.sels) + } in = append(in, reflect.ValueOf(traceCtx)) } if f.field.ArgsPacker != nil { diff --git a/selection/selected_field.go b/selection/selected_field.go new file mode 100644 index 00000000..7637564b --- /dev/null +++ b/selection/selected_field.go @@ -0,0 +1,8 @@ +package selection + +// SelectedField is the public representation of a field selection +// during a graphql query +type SelectedField struct { + Name string + SelectedFields []*SelectedField +}