Skip to content

Separate resolvers for each operation #561

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

Merged
merged 1 commit into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,50 @@ func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {
}
```

### Separate resolvers for different operations

The GraphQL specification allows for fields with the same name defined in different query types. For example, the schema below is a valid schema definition:
```graphql
schema {
query: Query
mutation: Mutation
}

type Query {
hello: String!
}

type Mutation {
hello: String!
}
```
The above schema would result in name collision if we use a single resolver struct because fields from both operations correspond to methods in the root resolver (the same Go struct). In order to resolve this issue, the library allows resolvers for query, mutation and subscription operations to be separated using the `Query`, `Mutation` and `Subscription` methods of the root resolver. These special methods are optional and if defined return the resolver for each opeartion. For example, the following is a resolver corresponding to the schema definition above. Note that there is a field named `hello` in both the query and the mutation definitions:

```go
type RootResolver struct{}
type QueryResolver struct{}
type MutationResolver struct{}

func(r *RootResolver) Query() *QueryResolver {
return &QueryResolver{}
}

func(r *RootResolver) Mutation() *MutationResolver {
return &MutationResolver{}
}

func (*QueryResolver) Hello() string {
return "Hello query!"
}

func (*MutationResolver) Hello() string {
return "Hello mutation!"
}

schema := graphql.MustParseSchema(sdl, &RootResolver{}, nil)
...
```

### Schema Options

- `UseStringDescriptions()` enables the usage of double quoted and triple quoted. When this is not enabled, comments are parsed as descriptions instead.
Expand Down
28 changes: 20 additions & 8 deletions example/starwars/starwars.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,22 +286,28 @@ var reviews = make(map[string][]*review)

type Resolver struct{}

func (r *Resolver) Hero(args struct{ Episode string }) *characterResolver {
func (*Resolver) Query() *QueryResolver {
return &QueryResolver{}
}

type QueryResolver struct{}

func (r *QueryResolver) Hero(args struct{ Episode string }) *characterResolver {
if args.Episode == "EMPIRE" {
return &characterResolver{&humanResolver{humanData["1000"]}}
}
return &characterResolver{&droidResolver{droidData["2001"]}}
}

func (r *Resolver) Reviews(args struct{ Episode string }) []*reviewResolver {
func (r *QueryResolver) Reviews(args struct{ Episode string }) []*reviewResolver {
var l []*reviewResolver
for _, review := range reviews[args.Episode] {
l = append(l, &reviewResolver{review})
}
return l
}

func (r *Resolver) Search(args struct{ Text string }) []*searchResultResolver {
func (r *QueryResolver) Search(args struct{ Text string }) []*searchResultResolver {
var l []*searchResultResolver
for _, h := range humans {
if strings.Contains(h.Name, args.Text) {
Expand All @@ -321,7 +327,7 @@ func (r *Resolver) Search(args struct{ Text string }) []*searchResultResolver {
return l
}

func (r *Resolver) Character(args struct{ ID graphql.ID }) *characterResolver {
func (r *QueryResolver) Character(args struct{ ID graphql.ID }) *characterResolver {
if h := humanData[args.ID]; h != nil {
return &characterResolver{&humanResolver{h}}
}
Expand All @@ -331,28 +337,34 @@ func (r *Resolver) Character(args struct{ ID graphql.ID }) *characterResolver {
return nil
}

func (r *Resolver) Human(args struct{ ID graphql.ID }) *humanResolver {
func (r *QueryResolver) Human(args struct{ ID graphql.ID }) *humanResolver {
if h := humanData[args.ID]; h != nil {
return &humanResolver{h}
}
return nil
}

func (r *Resolver) Droid(args struct{ ID graphql.ID }) *droidResolver {
func (r *QueryResolver) Droid(args struct{ ID graphql.ID }) *droidResolver {
if d := droidData[args.ID]; d != nil {
return &droidResolver{d}
}
return nil
}

func (r *Resolver) Starship(args struct{ ID graphql.ID }) *starshipResolver {
func (r *QueryResolver) Starship(args struct{ ID graphql.ID }) *starshipResolver {
if s := starshipData[args.ID]; s != nil {
return &starshipResolver{s}
}
return nil
}

func (r *Resolver) CreateReview(args *struct {
func (*Resolver) Mutation() *MutationResolver {
return &MutationResolver{}
}

type MutationResolver struct{}

func (r *MutationResolver) CreateReview(args *struct {
Episode string
Review *reviewInput
}) *reviewResolver {
Expand Down
2 changes: 1 addition & 1 deletion graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func (s *Schema) ValidateWithVariables(queryString string, variables map[string]
// without a resolver. If the context get cancelled, no further resolvers will be called and a
// the context error will be returned as soon as possible (not immediately).
func (s *Schema) Exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) *Response {
if !s.res.Resolver.IsValid() {
if !s.res.QueryResolver.IsValid() {
panic("schema created without resolver, can not exec")
}
return s.exec(ctx, queryString, operationName, variables, s.res)
Expand Down
Loading