Skip to content

Commit

Permalink
Separate resolvers for each operation
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelnikolov authored and KNiepok committed Feb 28, 2023
1 parent 9eae5d1 commit 431fde2
Show file tree
Hide file tree
Showing 8 changed files with 445 additions and 25 deletions.
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 @@ -247,7 +247,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")
}
execF := s.exec
Expand Down
Loading

0 comments on commit 431fde2

Please sign in to comment.