too many parameters returned by Resolver.ModuleName - go

I am working on breaking the monolithic architecture into Microservice architecture.
I did that but when I am building the code in my current repository I am getting this error.
We use graphql-gophers library
panic: too many parameters returned by (Resolver).Dummy
Has anyone ever seen this error in golang using graphql for querying?
Tried so many things but nothing has worked.
Any help would be appreciated

The error message comes from graph-gophers/graphql-go internal/exec/resolvable/resolvable.go#makeFieldExec
It is called when you parse a schema which does not match the field of an existing struct.
The one illustrated in example/customerrors/starwars.go does match every field and would not trigger the error message:
var Schema = `
schema {
query: Query
}
type Query {
droid(id: ID!): Droid!
}
# An autonomous mechanical character in the Star Wars universe
type Droid {
# The ID of the droid
id: ID!
# What others call this droid
name: String!
}
`
type droid struct {
ID graphql.ID
Name string
}
Its resolver does use the right parameters:
type Resolver struct{}
func (r *Resolver) Droid(args struct{ ID graphql.ID }) (*droidResolver, error) {
if d := droidData[args.ID]; d != nil {
return &droidResolver{d: d}, nil
}
return nil, &droidNotFoundError{Code: "NotFound", Message: "This is not the droid you are looking for"}
}
Try and use that example to check it does work, then modify it to transition to your own code.

Related

Golang gqlgen, fetching separate fields in a single DB query via Dataloading

I am trying to see if its possible to take several fields that have their own resolver and somehow get them in a single DB query since they are fetched from the same place.
Eg. the schema
type ExampleResposne {
A: Int!
B: Int!
C: Int!
}
The gqlgen.yml
models:
ExampleResponse:
fields:
A:
resolver: true
B:
resolver: true
C:
resolver: true
And now somehow I'd like to fetch A and B from a same DB call if they are requested at the same time
I have an API that returns these based on the keys
exampleRes, err := resolver.service.GetExample(ctx, GetExampleRequest{
Keys: []ExampleKey{
AKey,
BKey,
},
})
Is something like this possible or should I rethink my approach?
The generated resolvers
func (r *exampleResponseResolver) A(ctx context.Context, obj *model.ExampleResponse) (int, error) { panic(fmt.Errorf("not implemented"))
}
func (r *exampleResponseResolver) B(ctx context.Context, obj *model.ExampleResponse) (int, error) { panic(fmt.Errorf("not implemented"))
}
func (r *exampleResponseResolver) C(ctx context.Context, obj *model.ExampleResponse) (int, error) { panic(fmt.Errorf("not implemented"))
}
It seems you may have some topics confused:
Dataloading
The concept of dataloading is to batch multiple requests for data under one resolver's function / method call. This should be considered at the level of the resolving type and not at the level of individual fields of that type.
An example of this (taken from https://gqlgen.com/reference/dataloaders/) would be to resolve for multiple Users under one database request.
Graphql Field Resolvers
With your gqlgen.yml configuration, you have specified individual resolvers for fields with:
models:
ExampleResponse:
fields:
A:
resolver: true
...
The concept of field resolvers aim to resolve individual fields of a type rather than to resolve the type as a whole, essentially having the opposite effect to what dataloading has. This is useful when we are trying to resolve argumented fields or code splitting resolver logic.
To note, field resolvers can often cause N+1 problems when not used effectively.
The Solution
You might not be seeing a resolver for your type ExampleResponse because you need to append your queries entry-point under the Query type.
schema.graphql:
type Query {
getExample: ExampleResposne
}
Then run: go run github.com/99designs/gqlgen generate
You should see:
func (r *queryResolver) GetExample(ctx context.Context) (*model.ExampleResponse, error) {
panic(fmt.Errorf("not implemented"))
}
See https://graphql.org/learn/schema/#the-query-and-mutation-types for details.

Pattern for writing graphql schema with gorm decorator?

I am writing graphql schema that need to work with gorm, my understanding is that graphql can't inherently use gorm decorator, so to make it work, I wrote a separate Output type:
Let's say I have this table ORM:
type Character struct {
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
Name string `gorm:"unique" json:"name"`
CliqueType string `json:"clique"`
IsHero bool `json:"hero"`
}
This is hardcoded and put into a model.go file. The gorm decorator works here as this is native go file.
In the schema.graphql I have this instead:
type CharacterOutput {
id: ID!
name: String!
isHero: Boolean!
cliqueType: CliqueType!
}
input CharacterInput {
name: String!
isHero: Boolean
cliqueType: CliqueType
}
As you can see, I have both a input and output graphql schema definition. This allow graphql to generate the required type without conflicting with gorm.
The downside of this is that I need to write a separate manual mapper between the 2 type, which kind of defeat the purpose of a ORM:
func mapChar2Output(character *model.Character) *model.CharacterOutput {
output := model.CharacterOutput{
ID: strconv.FormatUint(uint64(character.ID), 10),
Name: character.Name,
CliqueType: model.CliqueType(character.CliqueType),
IsHero: character.IsHero,
}
return &output
}
func mapInputToChar(CharacterInput *model.CharacterInput, Character *model.Character) {
Character.Name = CharacterInput.Name
Character.IsHero = *CharacterInput.IsHero
Character.CliqueType = CharacterInput.CliqueType.String()
}
My graphql resolver look like this, notice I call the mapper after getting the result from repo:
// CreateCharacter is the resolver for the createCharacter field.
func (r *mutationResolver) CreateCharacter(ctx context.Context, input model.CharacterInput) (*model.CharacterOutput, error) {
result, err := r.Resolver.CharacterRepository.CreateCharacter(&input)
output := mapChar2Output(result)
return output, err
}
My database CRUD function call the input mapper:
func (b *CharacterService) CreateCharacter(CharacterInput *model.CharacterInput) (*model.Character, error) {
character := &model.Character{}
mapInputToChar(CharacterInput, character)
err := b.Db.Create(&character).Error
return character, err
}
What is a better way of designing this schema so graphql can work with gorm without needing to write any extra mapper?

Passing nested JSON as variable in Machinebox GraphQL mutation using golang

Hi there Golang experts,
I am using the Machinebox "github.com/machinebox/graphql" library in golang as client for my GraphQL server.
Mutations with single layer JSON variables work just fine
I am, however, at a loss as to how to pass a nested JSON as a variable
With a single layer JSON I simply create a map[string]string type and pass into the Var method. This in turn populates my graphql $data variable
The machinebox (graphql.Request).Var method takes an empty interface{} as value so the map[string]string works fine. But embedded json simply throws an error.
code:
func Mutate(data map[string]string, mutation string) interface{} {
client := GQLClient()
graphqlRequest := graphql.NewRequest(mutation)
graphqlRequest.Var("data", data)
var graphqlResponse interface{}
if err := client.Run(context.Background(), graphqlRequest, &graphqlResponse); err != nil {
panic(err)
}
return graphqlResponse
}
Mutation:
mutation createWfLog($data: WfLogCreateInput)
{
createWfLog (data: $data){
taskGUID {
id
status
notes
}
event
log
createdBy
}
}
data variable shape:
{
"data": {
"event": "Task Create",
"taskGUID": {
"connect": {"id": "606f46cdbbe767001a3b4707"}
},
"log": "my log and information",
"createdBy": "calvin cheng"
}
}
As mentioned, the embedded json (value of taskGUID) presents the problem. If value was simple string type, it's not an issue.
Have tried using a struct to define every nesting, passed in struct.
Have tried unmarshaling a struct to json. Same error.
Any help appreciated
Calvin
I have figured it out... and it is a case of my noobness with Golang.
I didn't need to do all this conversion of data or any such crazy type conversions. For some reason I got in my head everything HAD to be a map for the machinebox Var(key, value) to work
thanks to xarantolus's referenced site I was able to construct a proper strut. I populated the strut with my variable data (which was a nested json) and the mutation ran perfectly!
thanks!

Shall I create resolver for each query or for all of its fields in GraphQL?

I'm interested in using GraphQL and I've just started experimenting with it.
In a GraphQL tutorial, one can see the following quote:
Each field in a GraphQL schema is backed by a resolver.
But if you look at gqlgen (which is a golang library for building GraphQL servers) todo example that uses the following schema:
...
type MyQuery {
todo(id: ID!): Todo
lastTodo: Todo
todos: [Todo!]!
}
type MyMutation {
createTodo(todo: TodoInput!): Todo!
updateTodo(id: ID!, changes: Map!): Todo
}
type Todo {
id: ID!
text: String!
done: Boolean!
}
...
it actually uses 3 autogenerated resolvers (i.e., 1 for each query, not a field):
func (r *QueryResolver) Todo(ctx context.Context, id int) (*Todo, error) {
func (r *QueryResolver) LastTodo(ctx context.Context) (*Todo, error) {
func (r *QueryResolver) Todos(ctx context.Context) ([]*Todo, error) {
Is it the expected behavior not to autogenerate resolvers for each field (but for each query instead)?
When entire type is backed by a resolver then it's still true that each field is backed by resolver ;)
Comparing to SQL - usually you don't ask for each (single) field separately when you want entire record/row.
Field level resolvers are needed when type resolver don't return data for all required (by query) fields - f.e. for relations.

GraphQL mutation variables

I'm trying to do a simple mutation using GraphQL with the GraphiQL interface. My mutation looks like this:
mutation M($name: String) {
addGroup(name:$name) {
id,
name
}
}
with variables:
{
"name": "ben"
}
But it gives me the error: Variable $name of type "String" used in position expecting type "String!"
If I change my mutation to mutation M($name: String = "default") it works as expected. This looks like it's related to the type system, but I can't seem to figure out what the problem is.
You probably defined the input name as a non-null string (something like type: new GraphQLNonNull(GraphQLString) if using js server, or String! in plain GraphQL).
So your input in the mutation must match, which means it must also be a non-null string. If you change to the following, it should work:
mutation M($name: String!) {
addGroup(name:$name) {
id,
name
}
}
Also if you define a default value as you did, it will be a non-null string.
Finally, you could drop the requirement of being a non-null in the server.
I think in you addGroup() mutation the args for name is of type String! that is new GraphQLNonNull(GraphQLString) but in your mutation you specify as String which conflicts with the type system.
When you have an error like this check your database model, in my case I checked my schema and as mongo pluralizes the words was causing me error but I could fix it, also check the documentation.
mutation {
createProject(
name:"project two",
description:"project two"
) {
name
}
}
=> works

Resources