-
Notifications
You must be signed in to change notification settings - Fork 493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added possibility to explore the actual field selection tree #422
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package graphql | ||
|
||
import ( | ||
"context" | ||
|
||
gcontext "github.com/graph-gophers/graphql-go/internal/context" | ||
"github.com/graph-gophers/graphql-go/selected" | ||
) | ||
|
||
type Context struct { | ||
Field selected.Field | ||
} | ||
|
||
// GraphQLContext is used to retrieved the graphql from the context. If no graphql | ||
// is present in the context, the `fallbackGraphql` received in parameter | ||
// is returned instead. | ||
func GraphQLContext(ctx context.Context) *Context { | ||
field, found := gcontext.GraphQL(ctx) | ||
if !found { | ||
return nil | ||
} | ||
|
||
return &Context{ | ||
Field: field.ToSelection().(selected.Field), | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,12 +4,14 @@ | |
package starwars | ||
|
||
import ( | ||
"context" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changes here are just for demonstration purposes if someone wants to play with the feature a bit. To try it out, run the starwars example server and perform this
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not intend to ship that if the feature is merged, it will be removed. |
||
"encoding/base64" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
graphql "github.com/graph-gophers/graphql-go" | ||
"github.com/graph-gophers/graphql-go/selected" | ||
) | ||
|
||
var Schema = ` | ||
|
@@ -93,6 +95,8 @@ var Schema = ` | |
appearsIn: [Episode!]! | ||
# This droid's primary function | ||
primaryFunction: String | ||
|
||
|
||
} | ||
# A connection object for a character's friends | ||
type FriendsConnection { | ||
|
@@ -301,7 +305,10 @@ func (r *Resolver) Reviews(args struct{ Episode string }) []*reviewResolver { | |
return l | ||
} | ||
|
||
func (r *Resolver) Search(args struct{ Text string }) []*searchResultResolver { | ||
func (r *Resolver) Search(ctx context.Context, args struct{ Text string }) []*searchResultResolver { | ||
graphqlContext := graphql.GraphQLContext(ctx) | ||
selected.Dump(graphqlContext.Field) | ||
|
||
var l []*searchResultResolver | ||
for _, h := range humans { | ||
if strings.Contains(h.Name, args.Text) { | ||
|
@@ -338,7 +345,10 @@ func (r *Resolver) Human(args struct{ ID graphql.ID }) *humanResolver { | |
return nil | ||
} | ||
|
||
func (r *Resolver) Droid(args struct{ ID graphql.ID }) *droidResolver { | ||
func (r *Resolver) Droid(ctx context.Context, args struct{ ID graphql.ID }) *droidResolver { | ||
graphqlContext := graphql.GraphQLContext(ctx) | ||
selected.Dump(graphqlContext.Field) | ||
|
||
if d := droidData[args.ID]; d != nil { | ||
return &droidResolver{d} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package context | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/graph-gophers/graphql-go/internal/exec/selected" | ||
) | ||
|
||
type graphqlKeyType int | ||
|
||
const graphqlFieldKey graphqlKeyType = iota | ||
|
||
// WithGraphQLContext is used to create a new context with a graphql added to it | ||
// so it can be later retrieved using `Graphql`. | ||
func WithGraphQLContext(ctx context.Context, field *selected.SchemaField) context.Context { | ||
return context.WithValue(ctx, graphqlFieldKey, field) | ||
} | ||
|
||
// GraphQL is used to retrieved the graphql from the context. If no graphql | ||
// is present in the context, the `fallbackGraphql` received in parameter | ||
// is returned instead. | ||
func GraphQL(ctx context.Context) (field *selected.SchemaField, found bool) { | ||
if ctx == nil { | ||
return | ||
} | ||
|
||
if v, ok := ctx.Value(graphqlFieldKey).(*selected.SchemaField); ok { | ||
return v, true | ||
} | ||
|
||
return | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import ( | |
"github.com/graph-gophers/graphql-go/internal/query" | ||
"github.com/graph-gophers/graphql-go/internal/schema" | ||
"github.com/graph-gophers/graphql-go/introspection" | ||
"github.com/graph-gophers/graphql-go/selected" | ||
) | ||
|
||
type Request struct { | ||
|
@@ -44,6 +45,19 @@ func ApplyOperation(r *Request, s *resolvable.Schema, op *query.Operation) []Sel | |
|
||
type Selection interface { | ||
isSelection() | ||
ToSelection() selected.Selection | ||
} | ||
|
||
func toSelections(sels []Selection) (out []selected.Selection) { | ||
if len(sels) == 0 { | ||
return | ||
} | ||
|
||
out = make([]selected.Selection, len(sels)) | ||
for i, sel := range sels { | ||
out[i] = sel.ToSelection() | ||
} | ||
return | ||
} | ||
|
||
type SchemaField struct { | ||
|
@@ -56,16 +70,86 @@ type SchemaField struct { | |
FixedResult reflect.Value | ||
} | ||
|
||
func (f *SchemaField) Kind() selected.Kind { | ||
return selected.FieldKind | ||
} | ||
|
||
func (f *SchemaField) Identifier() string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't use At one point, if we all agree on the feature and the interface, I suggest the internal field changes to the interface looks better. This is the same reasoning for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why can't you use its own There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the new public interface would exposes it, hence a field with the same name cannot exist. |
||
return f.Name | ||
} | ||
|
||
func (f *SchemaField) Aliased() string { | ||
return f.Alias | ||
} | ||
|
||
func (f *SchemaField) Children() (out []selected.Selection) { | ||
return toSelections(f.Sels) | ||
} | ||
|
||
func (f *SchemaField) ToSelection() selected.Selection { | ||
return selected.Selection(f) | ||
} | ||
|
||
type TypeAssertion struct { | ||
resolvable.TypeAssertion | ||
Sels []Selection | ||
} | ||
|
||
func (f *TypeAssertion) Kind() selected.Kind { | ||
return selected.TypeAssertionKind | ||
} | ||
|
||
func (f *TypeAssertion) Type() string { | ||
var toType func(resolvable.Resolvable) string | ||
toType = func(r resolvable.Resolvable) string { | ||
if f.TypeExec == nil { | ||
return "" | ||
} | ||
|
||
switch v := f.TypeExec.(type) { | ||
case *resolvable.Scalar: | ||
return "scalar" | ||
case *resolvable.List: | ||
return toType(v.Elem) | ||
case *resolvable.Object: | ||
return v.Name | ||
default: | ||
return "<unknown>" | ||
} | ||
} | ||
|
||
return toType(f.TypeExec) | ||
} | ||
|
||
func (f *TypeAssertion) Children() (out []selected.Selection) { | ||
return toSelections(f.Sels) | ||
} | ||
|
||
func (f *TypeAssertion) ToSelection() selected.Selection { | ||
return selected.Selection(f) | ||
} | ||
|
||
type TypenameField struct { | ||
resolvable.Object | ||
Alias string | ||
} | ||
|
||
func (f *TypenameField) Kind() selected.Kind { | ||
return selected.TypenameFieldKind | ||
} | ||
|
||
func (f *TypenameField) Aliased() string { | ||
return f.Alias | ||
} | ||
|
||
func (f *TypenameField) Type() string { | ||
return f.Name | ||
} | ||
|
||
func (f *TypenameField) ToSelection() selected.Selection { | ||
return selected.Selection(f) | ||
} | ||
|
||
func (*SchemaField) isSelection() {} | ||
func (*TypeAssertion) isSelection() {} | ||
func (*TypenameField) isSelection() {} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package selected | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
type Kind int | ||
|
||
func (k Kind) String() string { | ||
switch k { | ||
case FieldKind: | ||
return "field" | ||
case TypeAssertionKind: | ||
return "type_assertion" | ||
case TypenameFieldKind: | ||
return "typename_field" | ||
default: | ||
panic(fmt.Errorf("invalid kind %d received", k)) | ||
} | ||
} | ||
|
||
const ( | ||
FieldKind Kind = iota | ||
TypeAssertionKind | ||
TypenameFieldKind | ||
) | ||
|
||
type Selection interface { | ||
Kind() Kind | ||
} | ||
|
||
type Field interface { | ||
Selection | ||
Identifier() string | ||
Aliased() string | ||
Children() []Selection | ||
} | ||
|
||
type TypeAssertion interface { | ||
Selection | ||
Type() string | ||
Children() []Selection | ||
} | ||
|
||
type TypenameField interface { | ||
Selection | ||
Type() string | ||
Aliased() string | ||
} | ||
|
||
func Dump(selection Selection) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also present mainly for debugging/exploration purposes, another method that could be removed. |
||
if selection == nil { | ||
fmt.Println("Selection <nil>") | ||
return | ||
} | ||
|
||
var print func(string, Selection) | ||
print = func(indent string, sel Selection) { | ||
switch v := sel.(type) { | ||
case Field: | ||
fmt.Printf(indent+"Field %s (%s)\n", v.Identifier(), v.Aliased()) | ||
for _, subSel := range v.Children() { | ||
print(indent+" ", subSel) | ||
} | ||
case TypeAssertion: | ||
fmt.Printf(indent+"TypeAssertion %s\n", v.Type()) | ||
for _, subSel := range v.Children() { | ||
print(indent+" ", subSel) | ||
} | ||
case TypenameField: | ||
fmt.Printf(indent+"TypenameField %s (%s)\n", v.Type(), v.Aliased()) | ||
default: | ||
panic(fmt.Errorf("invalid selection %T received", v)) | ||
} | ||
} | ||
|
||
print("", selection) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong comment