Graphql: How to get rootQuery param on resolver of nested field

Created on 11 Jan 2017  路  9Comments  路  Source: graphql-go/graphql

I have this structure:

func rootQuery() *graphql.Object {
    return graphql.NewObject(graphql.ObjectConfig{
        Name: "RootQuery",
        Fields: graphql.Fields{
            "user": &graphql.Field{
                Type:        userType,
                Args: graphql.FieldConfigArgument{
                    "id": &graphql.ArgumentConfig{
                        Type:        graphql.String,
                        Description: "Find user by id",
                    },
                    "email": &graphql.ArgumentConfig{
                        Type:        graphql.String,
                        Description: "Find user by email",
                    },
                },
                Resolve: FindUserResolver,
            },
        },
    })
}

And this is the userType :

var userType = graphql.NewObject(graphql.ObjectConfig{
    Name: "User",
    Fields: graphql.Fields{
        "id":            &graphql.Field{Type: graphql.String},
        "email":         &graphql.Field{Type: graphql.String},
        "address": &graphql.Field{
            Type: graphql.NewObject(graphql.ObjectConfig{
                Name: "UserAddress",
                Fields: graphql.Fields{
                    "state": &graphql.Field{
                        Type: graphql.NewObject(graphql.ObjectConfig{
                            Name: "StateDetails",
                            Fields: graphql.Fields{
                                "id":           &graphql.Field{Type: graphql.Int},
                                "name":         &graphql.Field{
                                    Type: graphql.String,
                                    Resolve: func (p *graphql.ResolveParams) (interface{}, error){
                                        // I NEED USER ID OR USER EMAIL HERE
                                        return nil, nil
                                    }
                                },
                                "abbreviation": &graphql.Field{Type: graphql.String},
                            },
                        }),
                    },
                },
            }),
        },
    },
})

In Graphql spec we have this: http://graphql.org/learn/execution/#root-fields-resolvers

__"obj The previous object, which for a field on the root Query type is often not used."__

How I can get id or email field value on this address->name resolver ?
In p *graphql.ResolveParams we have params on sub level, not on root level.
Do we have something like obj to access to the previous object in this implementation?

Query example:

{
  user(
    id: "19966307-1505-4f1c-ba69-e8d1fcab1eda"),
    {
      address{
        state{
          name
        }
      }
    }
}

Thanks.

Most helpful comment

@vsouza if I understood you correctly you're trying to get the arguments to a field a couple of levels higher up in the graph hierarchy.

Although I didn't have the need yet to do this myself I think this may be possible by inspecting the FieldASTs field of the ResolveInfo that gets passed to your resolver. The ast.Field contains a list of Arguments which I believe holds the information you want to have (I didn't have the time to try it out).

edit: seems like my assumption was wrong because the FieldASTs field of ResolveInfo only contains the current level of the graph.

One way to solve this issue that comes to my mind then would be adding a UserID/Email field to the structs of the parent resolvers (maybe excluded from the JSON serialization, although it shouldn't make a difference if your schema doesn't expose that field) and use this via ResolveParams.Source.

Another possible solution would be to simply return the full struct for the state field from the UserAddress resolver if it's not too expensive to get.

All 9 comments

Something like this:

// Structure for setting ID and Email
Name struct {
  Id int64
  Email string
}
// fields for the structure
nameType := graphql.NewObject(graphql.ObjectConfig{
        Name: "nameType",
        Fields: graphql.Fields{
            "user": &graphql.Field{
                Type:        userType,
                Args: graphql.FieldConfigArgument{
                    "id": &graphql.ArgumentConfig{
                        Type:        graphql.String,
                    },
                    "email": &graphql.ArgumentConfig{
                        Type:        graphql.String,
                    },
                },
                Resolve: FindUserResolver,
            },
        },
    })
// part of your userType 
Type: graphql.NewObject(graphql.ObjectConfig {
    Name: "StateDetails",
    Fields: graphql.Fields {
        "id": &graphql.Field {
            Type: graphql.Int
        },
        "name": &graphql.Field {
            Type: nameType,                 // <- your type for both fields (id & email)
            Resolve: func(p *graphql.ResolveParams)(interface {}, error) {
                var nameField Name
                nameField.Id = 123
                nameField.Email = "[email protected]"

                return nameField, nil
            }
        },
        "abbreviation": & graphql.Field {
            Type: graphql.String
        },
    },
})

@incu6us thanks for help.

I didn't understand correctly, how can I get query value (id for example) on this Resolve if I pass struct on type I will have value of rootQuery?

@vsouza try to look to the project: https://github.com/graphql/graphiql. It will solve all your questions about query building

@incu6us query it's not my problem for now. My doubt it's about rootParams on nested fields resolvers. 馃槩

for use an arguments in your resolver u need to do:

// part of your userType 
Type: graphql.NewObject(graphql.ObjectConfig {
    Name: "StateDetails",
    Fields: graphql.Fields {
        "id": &graphql.Field {
            Type: graphql.Int
        },
        "name": & graphql.Field {
            Type: nameType, // <- your type for both fields (id & email)
            Args: graphql.FieldConfigArgument {
                "id": &graphql.ArgumentConfig {
                    Type: graphql.String,
                    Description: "Find user by id",
                },
                "email": &graphql.ArgumentConfig {
                    Type: graphql.String,
                    Description: "Find user by email",
                },
            },
            Resolve: func(p * graphql.ResolveParams)(interface {}, error) {
                id, _ := strconv.Atoi(p.Args["id"].(string))
                email, _ := p.Args["email"].(string)

                var nameField Name
                nameField.Id = id
                nameField.Email = email

                return nameField, nil
            }
        },
        "abbreviation": &graphql.Field {
            Type: graphql.String
        },
    },
})

@vsouza if I understood you correctly you're trying to get the arguments to a field a couple of levels higher up in the graph hierarchy.

Although I didn't have the need yet to do this myself I think this may be possible by inspecting the FieldASTs field of the ResolveInfo that gets passed to your resolver. The ast.Field contains a list of Arguments which I believe holds the information you want to have (I didn't have the time to try it out).

edit: seems like my assumption was wrong because the FieldASTs field of ResolveInfo only contains the current level of the graph.

One way to solve this issue that comes to my mind then would be adding a UserID/Email field to the structs of the parent resolvers (maybe excluded from the JSON serialization, although it shouldn't make a difference if your schema doesn't expose that field) and use this via ResolveParams.Source.

Another possible solution would be to simply return the full struct for the state field from the UserAddress resolver if it's not too expensive to get.

Are there any news on this or a complete example? It's not cheap to always fetch the whole graph, when only a few fields need to be resolved.

I have the same use-case, which essentially gets illustrated by this example query:

query {
  book(isbn: "9780674430006") {
    title 
    authors {
      name
    }
  }
}

Authors and books are saved in to different databases/tables. Now a book gets looked up by isbn, but the database schema does not contain the authors directly rather the authors-field needs to resolved by looking for the authors using the book's id (not isbn in this example).

How can I add a nested resolver receiving the required information of the parent object?

Looking at how it is done in nodejs, the resolver function receives the resolved parent as well: https://gist.github.com/xpepermint/7376b8c67caa926e19d2#file-graphql-schema-js-L55
Is this what the ResolveParams.Source is for?

@trevex In your resolver for authors you do your necessary lookup for the author however you need, at this point you have the data affiliated with the fetched book. Assuming you have a struct that is at least:

type Book struct {
        ISBN  string
        ID    uint64
        Title string
}

You return this from the book resolve function, and then title's resolve can be automatic (if you have json tags on the struct) and then for authors, you do whatever you have to like:

//...
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
        book, ok := params.Source.(*Book) // assumes *Book was returned by the Book resolver
        if !ok {
                return nil, errors.New("no book received by author resolver")
        }
        authors, err := authors.FindByBookID(book.ID)

        return authors, err
},
//...

Then assuming that authors here would be something like this AuthorInformation (or other similar thing):

type AuthorInformation struct {
        Authors []Author
        BookID   unit64
}

Then inside the resolution for an Author you have your book details. Basically whatever you return in an object resolver is the params.Source of it's children. You're in 100% control of this, so it can be whatever you need to accomplish your goal.

@vsouza I know I didn't use your example here, but this concept should also clear up your issue, if I understood correctly. If not, can you get your problem codified into a single file I can compile and run locally to figure out how to better help you?

@bbuck & @sfriedel thanks for your help. For solve this, I changed my project structure, now I have a resolver in primary level and I check if I have address->name on params.Info.FieldsASTs, so I do what I have to do.

I'll test your solution, it's for long more clear.

Thanks guys.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rubberviscous picture rubberviscous  路  4Comments

titanous picture titanous  路  3Comments

sanae10001 picture sanae10001  路  5Comments

rustysys-dev picture rustysys-dev  路  6Comments

amedeiros picture amedeiros  路  3Comments