shurcool / graphql Goto Github PK
View Code? Open in Web Editor NEWPackage graphql provides a GraphQL client implementation.
License: MIT License
Package graphql provides a GraphQL client implementation.
License: MIT License
In my case, the server that I depend on should be mocked, and I need to verify the request in the HTTP handler, so I think it's better to expose some tool functions which can help us to encode/decode.
I'm working with a graphQL API that has a top level resource that has several fields in it that are paginated themselves - something like
{
account {
services(after: "", first: 10) {
nodes {
tools(after: "", first: 2) {
nodes {
category
}
pageInfo {
endCursor
hasNextPage
}
}
tags(after: "", first: 2) {
nodes {
key
value
}
pageInfo {
endCursor
hasNextPage
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
and its unclear to me how to properly handle pagination of these nested tools
and tags
resources. I understand how the pageInfo
object's endCursor
and hasNextPage
fields work and how to use them with the argument to after
to query the next page of data but its unclear to me the proper pattern to use in go code when using this graphql framework.
I would love if anyone has links to example code query-ing nested pagination or blog posts talking about how to do pagination with this framework.
Thanks! Love the framework so far!
Hello,
Thank you for this amazing library, I'm trying to insert an object by executing an insert mutation.
So what I have is a simple table called events, with two properties id, event. So what I try to do is create an object with these two properties and insert it. However I'm not sure how to do it? Can someone have a quick look?
listEvents := []*Events{}
listEvents = append(listEvents, &Events{
Event: graphql.String("test event"),
})
var mutation struct {
CreateEvents struct {
Event string
} `graphql:"CreateEvents(events:$events)"`
}
variables := map[string]interface{}{
"event": listEvents,
}
if err := client.Mutate(context.Background(),&mutation, variables); err != nil {
fmt.Println(err)
}
The error I get is this..
non-200 OK status code: 400 Bad Request body: "{\"errors\":[{\"extensions\":{\"path\":\"$\",\"code\":\"validation-failed\"},\"message\":\"variables can only be defined on input types(enums, scC02XL1E5JHD3:graphC02XL1E5JHD3:grC02XL1C02XL1C02XL1E5JHD3C02XL1E5JC02C02XL1C02C02XL1E5JC02XL1E5JHD3:grC02XL1E5JHD3:grC02XL1C02C02XL1E5JHD3:grC02XL1C02XL1C02XL1E5JHD3C02XL1E5JHD3C02C02XL1E5JHD3C02
Hello,
I was wondering if its possible to use alternative HTTP clients or how much work it would be to support this.
For example i'm looking to add retry & backoff to my graphql client based on top of this library and there are a handful of libraries out there such as https://github.com/go-resty/resty
But it seems like most act as a wrapper ontop of http.Client and there is no way to pass in a https://github.com/go-resty/resty client to be used at the http.Client and retain all functionality for things like retry & backoff.
Am I thinking about this incorrectly and is there a way to solve retry & backoff without using a http.Client wrapper?
Thanks!
struct field for status doesn't exist in any of 2 places to unmarshal
I have a JSON type and it seems to be expecting the whole object before running my unmarshal.
is there any way to ignore this given it's a JSON scalar return?
Hi! when I try to execute a mutation with a list of ProductInput structs, I receive in the server a list of products but the attributes (Name and Location) are empty. According to the documentation, it is the way to do it, what I'm doing wrong? Many thanks:
type ProductInput struct {
Name graphql.String
Location graphql.String
}
listProducts := []*ProductInput{}
for _, element := range installInfo.Package.Products {
listProducts = append(listProducts, &ProductInput{
Name: graphql.String(element.Name),
Location: graphql.String(element.Image),
})
}
var mutation struct {
CreateInstallation struct {
ID string
} `graphql:"CreateInstallation(installation:{id: $installation, package: {products:$products}})"`
}
variables := map[string]interface{}{
"installation": graphql.UUID(installInfo.UUID.String()),
"products": listProducts,
}
if err := k.client.Mutation(&mutation, variables); err != nil {
return err
}
Currently having some issues with fragments.
I am able to correctly send a query with fragments, but the response get lost while unmarshalling. From my understanding, the reason is because queries with fragments and responses have different structures. The response cannot be unmarshalled into the original query.
For example, this query with a fragment:
type query struct {
TagSearch struct {
Error struct {
Message graphql.String
Type struct {
Class graphql.String
ErrorTypeInvalidRequest struct {
Fields []struct {
Field graphql.String
Message graphql.String
}
} `graphql:"... on ErrorTypeInvalidRequest"`
}
}
Tags []Tag
} `graphql:"tagSearch(contextIds: $contextIds, limit: $limit, primary: $primary, public: $public, query: $query)"`
}
Can only be marshaled to the following struct:
type query struct {
TagSearch struct {
Error struct {
Message graphql.String
Type struct {
Class graphql.String
Fields []struct {
Field graphql.String
Message graphql.String
}
}
}
Tags []Tag
} `graphql:"tagSearch(contextIds: $contextIds, limit: $limit, primary: $primary, public: $public, query: $query)"`
}
Where the ErrorTypeInvalidRequest
struct was removed.
It is strange because in the README two different structs are actually used. One for the query and one for the response, but this is not mentioned and not actually possible from the library.
fmt.Println(q.Hero.PrimaryFunction)
would give you a compilation error. fmt.Println(q.Hero.DroidFragment.PrimaryFunction)
is available instead.
I want to use the Hasura GraphQL API.
query getTenantDetails {
tenant_by_pk(id: $id) {
id
slug
project {
id
endpoint
}
}
}
I defined the struct below and run with these script.
var GetTenantDetails struct {
TenantByPK struct {
//ID graphql.ID
Cloud graphql.String
Region graphql.String
} `graphql:"tenant_by_pk(id: $id)"`
}
q := GetTenantDetails
vars := map[string]interface{}{
"id": graphql.ID("my-id"),
}
if err := client.Query(ctx, &q, vars); err != nil {
log.Fatal(err)
}
But I get this weird error.
Error: variable id of type ID! is used in position expecting uuid!
How can I use uuid
? I read the GoDoc but I couldn't find any types for UUID.
Thanks in advance.
Minor bug where the response from the server is not compliant with the Query Struct and provides valid graphql Errors, but returns too early so error supplied is from Unmarshal and not the actual server Error.
Fix could be in graphql.go (line 85) as such:
if err != nil {
if err.Error() == "unexpected end of JSON input" {
//If you return here, then you will not return the actual Errors supplied by the server
fmt.Println("There was a problem Unmarshalling the response into the provided Model Struct")
} else {
return err
}
}
From:
if err != nil {
return err
}
How would you code the following mutation that returns an ID upon creation:
mutation CreateTeamPipeline {
teamPipelineCreate(input: {teamID: "...", pipelineID: "...", accessLevel: READ_ONLY}) {
teamPipeline {
id
}
}
}
From #45 (comment):
this package should do something better in this situation. Either the error message needs to be improved, or maybe it could unmarshal just the first book and drop the rest.
See issue #45 for more context.
/cc @starjasmine
(Originally mentioned at #9, but filing as a separate issue for clarity of discussion.)
Structs with fields that reference themselves (or reference other types that eventually reference themselves) result in a stack overflow as the query generator code traverses them.
For example, this is a problematic struct containing fields with its own type:
type Foobar struct {
Children []Foobar
}
This is the relevant part of the stack trace resulting:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
goroutine 7 [running]:
... some frames elided ....
theproject/vendor/github.com/shurcooL/graphql.writeQuery(0x15b3be0, 0xc42017f180, 0x15be320, 0x139e100, 0x0)
/theproject/vendor/github.com/shurcooL/graphql/query.go:103 +0x93 fp=0xc4402005c0 sp=0xc4402004c0 pc=0x1303dd3
theproject/vendor/github.com/shurcooL/graphql.writeQuery(0x15b3be0, 0xc42017f180, 0x15be320, 0x13ba360, 0x133be00)
/theproject/vendor/github.com/shurcooL/graphql/query.go:123 +0x11f fp=0xc4402006c0 sp=0xc4402005c0 pc=0x1303e5f
theproject/vendor/github.com/shurcooL/graphql.writeQuery(0x15b3be0, 0xc42017f180, 0x15be320, 0x1358c40, 0x0)
/theproject/vendor/github.com/shurcooL/graphql/query.go:100 +0x3fa fp=0xc4402007c0 sp=0xc4402006c0 pc=0x130413a
theproject/vendor/github.com/shurcooL/graphql.writeQuery(0x15b3be0, 0xc42017f180, 0x15be320, 0x13ac720, 0x133be00)
/theproject/vendor/github.com/shurcooL/graphql/query.go:123 +0x11f fp=0xc4402008c0 sp=0xc4402007c0 pc=0x1303e5f
... loop continues between 100 and 123 ...
Basically, the writeQuery
function as currently written is perfectly happy to keep looking up the type of the Foobar
struct, looking at the Children
field and generating a little more query, then recursing to look up the type of the field, which is of course still Foobar
...
It would be nicer if the library detected this cycle and raised an error with a relevant message as soon as possible. A panic still seems reasonable (IMO), because you're pretty much statically doomed from the time you compiled the program and have no hope of handling this; but a stack overflow as that panic is a bit of a bummer.
Do you like add request log on graphql client request
like:
{"level": "INFO", "query":"{"query":"queryQl","mutation":"mutationQL"}", "variables":{"args":"args"}}
Hi, thanks for any help you may offer.
I need to mutate using this graphql mutation quide: https://shopify.dev/docs/admin-api/graphql/reference/mutation/inventoryadjustquantity?api[version]=2020-07
I looked here at the mutations example but surely I missed something or wrongfully applied the logic because I cannot get it to work: https://github.com/shurcooL/graphql
import (
"context"
"fmt"
shopifygraphql "github.com/r0busta/go-shopify-graphql"
"github.com/shurcooL/graphql"
)
func main() {
// Create client
opts := []shopifygraphql.Option{
shopifygraphql.WithVersion("2020-07"),
shopifygraphql.WithPrivateAppAuth("myappkey", "myapppass"),
}
client := shopifygraphql.NewClient("myshopify", opts...)
type InventoryAdjustQuantityInput struct {
InventoryLevelId graphql.String
AvailableDelta graphql.Int
}
var m struct {
InventoryAdjustQuantity struct {
InventoryLevel struct {
ID graphql.String
}
} `graphql:"inventoryAdjustQuantity(input: $input)"`
}
vars := map[string]interface{}{
"input": InventoryAdjustQuantityInput{
InventoryLevelId: graphql.String("gid://shopify/Product/5544934473882"),
AvailableDelta: graphql.Int(5),
},
}
err := client.Mutate(context.Background(), &m, vars)
if err != nil {
panic(err)
}
fmt.Printf("Inventory adjusted %v\n", m.InventoryAdjustQuantity.InventoryLevel.ID)
}
I am accessing a GraphQL API that returns some data which has the type "array of tuples" (specifically [string, {min: number, max: number}][]
in typescript), but I am unable to find a way to formulate this with go structs and this package. Is there a way of fetching what is equivalent to tuples using this package?
Can someone please provide a query example which uses @skip
or @include
directive?
Have you checked out graphql-go/graphql?
Is it possible to consolidate the two?
I ask here because the other one appears to be older.
Re:
Line 11 in 52ce28a
Suppose I can create user like this
mutation($login: String!) {
createUser(login: $login) { login }
}
variables {
"login": "grihabor"
}
Now I'd like to create multiple users in single request
mutation($login1: String!, $login2: String!, $login3: String!) {
createUser(login: $login1) { login }
createUser(login: $login2) { login }
createUser(login: $login3) { login }
}
variables {
"login1": "grihabor",
"login2": "diman",
"login3": "indigo",
}
It would be convenient to implement it in code with ordered map
type CreateUser struct {
Login graphql.String
}
m := [][2]interface{}{
{"createUser(login: $login1)", &CreateUser{}},
{"createUser(login: $login2)", &CreateUser{}},
{"createUser(login: $login3)", &CreateUser{}},
}
variables := map[string]interface{}{
"login1": "grihabor",
"login2": "diman",
"login3": "indigo",
}
_ = client.Mutate(context.Background(), &m, variables)
jfyi I've started implementing the thing
I defined a struct of post like below
type Post struct {
_ID graphql.ID
Title graphql.String
}
it gives me error message
struct field for "_id" doesn't exist in any of 1 places to unmarshal
while it give me error message
Message: Cannot query field 'id' on type 'Post'. Did you mean '_id'? (line 1, column 47):
query ($postId:ID!){findPostByID(id: $postId){id,title}}}
if i define the struct field like
type Post struct {
ID graphql.ID
Title graphql.String
}
I'm using FaunaDB graphql by the way, where their id reference is _id
, is there any way to solve this one ?
The response body would be very useful for debugging error messages for unexpected statuses. Many 4xx and 5xx responses include useful explanations in their body text.
In particular the https://github.com/kubernetes/test-infra repo uses the https://github.com/shurcooL/githubv4 library to interact with GitHub's v4 API and we are seeing lots of 502 and 403 errors that should include explanations in the response bodies.
ref: kubernetes/test-infra#7466
The error is generated here:
Lines 72 to 74 in 3658993
ReadAll
of the response body and appending it to the error text would make this error message much more useful.
I'd be happy to PR this change if this seems reasonable.
See issue 19.
Structs used in variables need to have json
tags to match the names that the GraphQL server expects. E.g.:
type ProductInput struct {
Name graphql.String `json:"name"`
Location graphql.String `json:"location"`
}
This is not intuitive, and currently not documented.
I should either:
Document this so it's clear this must be done.
Improve the graphql
code to make it no longer neccessary to manually specify those json
tags.
They can be inferred from the field name, just need to convert from Go's MixedCaps case to GraphQL's lowerCamelCase (this is already being done for queries, the code for it exists in ident
package).
Hello and thanks for the work that you already did put into this!
I want to talk to a GraphQL Server. I can only consume it, I have no influence on the API.
My query looks roughly like this:
var query1 struct {
member struct {
n1 graphql.Int
s1 graphql.String
} `graphql:"parameters: { p1: \"123\", p2: 123}"`
}
Please see that the query arguments have a struct as parameter.
I can talk fine manually to the server using GraphiQL or Postman.
When I use golang with this lib, it fails to parse the list or arguments with:
2020/09/28 16:43:22 Invalid Syntax : offending token '{' at line 1 column 16
Please ignore the exact column number, I abstracted the query
Many thanks for looking into this.
First off, just to clear the air -- GraphQL doesn't really support recursion. This is a conscious design decision by the GraphQL developers, and it's one I largely understand and agree with. Recursive queries are a great way to accidentally generate unbounded load on your servers.
However, that's not to say some finite amount of recursion isn't useful. It's possible to come up with situations where a query should use some type A
which contains SomeField A[]
. The recursion might be limited at some depth -- possibly 1, possibly 2; any finite number -- even 1 is significant.
So, as formulated nicely in an upstream issue in GraphQL, this sort of query for example is sometimes useful:
{
messages {
...CommentsRecursive
}
}
fragment CommentsRecursive on Message {
comments {
...CommentFields
comments {
...CommentFields
comments {
...CommentFields
}
}
}
}
fragment CommentFields on Comment {
id
content
}
I've been experimenting with this, and it seems like it's not a huge problem to do it on the server side with neelance/graphql-go, happily (it just terminates recursion when it runs out of data, and implementing that is Our Problem, but that's fine).
This client library, however, currently gets hung up if we use a single type that contains itself, like this:
type Foobar struct {
Children []Foobar
}
The current hangup isn't because the library blocks recursive queries, but rather because it's a tad too enthusiastic about recursing... forever. This is the relevant part of the stack trace resulting:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
goroutine 7 [running]:
... some frames elided ....
theproject/vendor/github.com/shurcooL/graphql.writeQuery(0x15b3be0, 0xc42017f180, 0x15be320, 0x139e100, 0x0)
/theproject/vendor/github.com/shurcooL/graphql/query.go:103 +0x93 fp=0xc4402005c0 sp=0xc4402004c0 pc=0x1303dd3
theproject/vendor/github.com/shurcooL/graphql.writeQuery(0x15b3be0, 0xc42017f180, 0x15be320, 0x13ba360, 0x133be00)
/theproject/vendor/github.com/shurcooL/graphql/query.go:123 +0x11f fp=0xc4402006c0 sp=0xc4402005c0 pc=0x1303e5f
theproject/vendor/github.com/shurcooL/graphql.writeQuery(0x15b3be0, 0xc42017f180, 0x15be320, 0x1358c40, 0x0)
/theproject/vendor/github.com/shurcooL/graphql/query.go:100 +0x3fa fp=0xc4402007c0 sp=0xc4402006c0 pc=0x130413a
theproject/vendor/github.com/shurcooL/graphql.writeQuery(0x15b3be0, 0xc42017f180, 0x15be320, 0x13ac720, 0x133be00)
/theproject/vendor/github.com/shurcooL/graphql/query.go:123 +0x11f fp=0xc4402008c0 sp=0xc4402007c0 pc=0x1303e5f
... loop continues between 100 and 123 ...
Basically, the writeQuery
function as currently written is perfectly happy to keep looking up the type of the Foobar
struct, looking at the Children
field and generating a little more query, then recursing to look up the type of the field, which is of course still Foobar
...
Once solution to this issue is "don't have recursive queries".
If you can get away with that, great. If trying to shoehorn that in to a situation where the types really are correctly represented as recursive, ehm.
We tried doing a couple of copypasta types -- e.g. just a T1, T2, T3, etc, where T1 has a field that's a slice of T2, etc -- but this approach does not come out very elegant: those almost-identical types become visible to more other code in the project than we'd like.
The GraphQL upstream opinion seems to be that finite and explicit recursion is fine -- just pick a depth. I'd propose we teach this library how to do that. The main question is how to communicate it to the library, since the structs and tags are where all the communication currently goes on.
My rough proposal would be adding a map to the writeQuery
function where it keeps track of where it's visited, and increments the map values on each visit. This map could be keyed by a tuple of {t reflect.Type, fieldOffset int}
, which marks the recursion edge itself. This would also correspond nicely with adding more info to the tag on the struct field itself for how much we want to limit recursion.
How to add another parameter to the field tags is a little more interesting. Since the entire quoted section of the tag is current graphQL snippet, perhaps the clearest thing to do would actually be to add a second tag, like so: SomeField TypeX
graphql:"... on foobar",graphql-recursion:3` I'm definitely open to other designs on that though.
There are some limits to this approach, namely that the tags approach leaves us sort of stuck declaring the recursion depths statically at compile time. But that's roughly consistent with the library's approach as a whole, and seems to be a pretty acceptable trade to me.
WDYT?
See https://tip.golang.org/doc/go1.10#reflect. As a result, there are certain types of queries that we won't be able to unmarshal into successfully. Need to decide where to detect the situation, and how to deal with it. (The current code may panic for such queries.)
The fix will be roughly similar to encoding/json
changes in 1.10.
I've already started work on this locally.
Whilst trying to use updatePullRequestReview
mutation I get a panic when calling client.mutate
I suspect that I have made a mistake with my mutation structs but the library really shouldn't panic and should instead provide information on the error
NOTE: the mutation is successfully applied
Sample code below to aid in replicating.
func updateReview(ctx context.Context, client *githubv4.Client, reviewID, Body string) error {
uuid, err := uuid.NewRandom()
if err != nil {
return err
}
var mut = githubv4.String(uuid.String())
input := githubv4.UpdatePullRequestReviewInput{
Body: githubv4.String(Body),
ClientMutationID: &mut,
PullRequestReviewID: reviewID,
}
var m struct {
updatePullRequestReview struct {
ClientMutationID string
} `graphql:"updatePullRequestReview(input: $input)"`
}
return client.Mutate(context.Background(), &m, input, nil)
}
Full Panic
panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
goroutine 1 [running]:
reflect.valueInterface(0x12374c0, 0xc4201d4370, 0x36, 0x1098601, 0x123ed20, 0x12374c0)
/usr/local/Cellar/go/1.10.3/libexec/src/reflect/value.go:959 +0x1c1
reflect.Value.Interface(0x12374c0, 0xc4201d4370, 0x36, 0x12374c0, 0xc4201d4370)
/usr/local/Cellar/go/1.10.3/libexec/src/reflect/value.go:948 +0x44
.../vendor/github.com/shurcooL/graphql/internal/jsonutil.unmarshalValue(0x123ed20, 0xc420010180, 0x123ed20, 0xc4201d4370, 0x1b8, 0xc420084480, 0x2)
.../vendor/github.com/shurcooL/graphql/internal/jsonutil/graphql.go:307 +0xca
.../vendor/github.com/shurcooL/graphql/internal/jsonutil.(*decoder).decode(0xc420069bd0, 0xc42024a3e0, 0x16)
.../vendor/github.com/shurcooL/graphql/internal/jsonutil/graphql.go:147 +0x45b
.../vendor/github.com/shurcooL/graphql/internal/jsonutil.(*decoder).Decode(0xc420069bd0, 0x1237ac0, 0xc4201d4370, 0xc420069bf8, 0x12375c0)
.../vendor/github.com/shurcooL/graphql/internal/jsonutil/graphql.go:66 +0x13d
.../vendor/github.com/shurcooL/graphql/internal/jsonutil.UnmarshalGraphQL(0xc4204e2300, 0x57, 0x60, 0x1237ac0, 0xc4201d4370, 0x12a3fed, 0x10)
.../vendor/github.com/shurcooL/graphql/internal/jsonutil/graphql.go:23 +0x12c
.../vendor/github.com/shurcooL/graphql.(*Client).do(0xc420114340, 0x12d5a80, 0xc4200a2008, 0x4e4e312d1f4d2b01, 0x1237ac0, 0xc4201d4370, 0xc42013c3c0, 0x0, 0x0)
.../vendor/github.com/shurcooL/graphql/graphql.go:85 +0x393
.../vendor/github.com/shurcooL/graphql.(*Client).Mutate(0xc420114340, 0x12d5a80, 0xc4200a2008, 0x1237ac0, 0xc4201d4370, 0xc42013c3c0, 0xc42013c390, 0xc42013c390)
.../vendor/github.com/shurcooL/graphql/graphql.go:43 +0x65
.../vendor/github.com/shurcooL/githubv4.(*Client).Mutate(0xc4200b0020, 0x12d5a80, 0xc4200a2008, 0x1237ac0, 0xc4201d4370, 0x1270140, 0xc42013c390, 0xc42013c3c0, 0x0, 0x0)
.../vendor/github.com/shurcooL/githubv4/githubv4.go:55 +0xc9
main.updateReview(0x12d5a80, 0xc4200a2008, 0xc4200b0020, 0xc42001ed20, 0x28, 0x12a2613, 0x9, 0xc420014880, 0x11)
.../github-commenter.go:101 +0x21a
main.main()
.../github-commenter.go:39 +0x167
exit status 2
Hello Reader,
I love this library. It makes maintaining and even generating graphql clients super easy. While the approach of using structs is not as flexible when you need very specific custom queries as other approaches I do enjoy the type safety and ability to generate the structs programmatically from the graphql schema.
I've been using this library in my production code for over a year now but I've noticed that there have not been any meaningful code changes to this repo since 2018.
There are also plenty of open PRs and Issues with great improvements and suggestions and a handful of questions that need to be documented or closed.
This makes me feel this repo is no longer being maintained and that since i'm relying upon this library in production facing code I need to do 1 of 2 things to protect my interests.
I've seen talk of a few other forks out there each with their own features but nothing universal. https://github.com/hasura/go-graphql-client seems to have the most newer features.
Since this library is a core utility to several production products at my company we are willing to take on maintainership.
@dmitshur or @shurcooL - is there something that we can do to help with maintainership? I really don't want to fork this and start maintaining my own as that just fractures the community even more but new features and improvements need to be added to help keep this repo alive and working well. Some of the bigger things to me are:
There is lots of work I'd like my team to contribute back with so everyone benefits but if there is no active maintainership here then I'm left to forking :(
Hello,
the private API that I want to access needs an enumeration type in one of the parameters.
https://graphql.org/learn/schema/#enumeration-types
Is there a way to implement this with this library?
Many thanks
Based on the usage text and also the example shown here. It seems to me that the client will need to be told upfront what the graphql query
would be before it can make the call.
Is there a way or example that show case dynamically construction of the query struct
? In another words, can I use the client to perform any graphql call without preemptively knowing how the query would look like?
Good time of the day!
I'm having an issue with unmarshalling a server reply payload into Golang's map[string]interface{}
.
The server uses gqlgen and the inbuilt scalar Map! in both input and output.
The mutation input and output in the server schema:
type Mutation {
fooMutation(input: MutationInput!): MutationPayload
}
input MutationInput {
inputMap: Map!
}
type MutationPayload {
outputMap: Map!
}
The structs used on the client side:
type MutationInput struct {
InputMap map[string]interface{} `json:"inputMap"`
}
type FooMutation struct {
MutationPayload struct {
OutputMap map[string]interface{} `json:"outputMap"`
} `graphql:"fooMutation(input: $mutationInput)"`
}
The mutation executes correctly and returns json of the form (got this from a Wireshark capture of the server reply):
{
"data": {
"fooMutation": {
"outputMap": {
"Date": "Tue, 24 Nov 2020 14:20:36 GMT",
"AnotherKey": "AnotherValue"
}
}
}
}
Error on the client side: struct field for "Date" doesn't exist in any of 1 places to unmarshal
I need OutputMap
to be of type map[string]interface{}
and not struct
as the keys and the number of keys may vary. Am I doing something wrong or is it so that the library can't work with Golang maps?
I've tried executing the mutation using a plain http client with json and the mutation and unmarshalling of the payload work without a problem.
Thank you for your help,
A
For our use case, we need to pass in our custom HttpClient and make request with a different OAuth2 Bearer Token. We can't use the oauth2 lib as suggested. Anyway to get access to the req.headers before sending mutation queries?
I have a custom HttpClient such as this:
type AddHeaderTransport struct {
T http.RoundTripper
}
func (adt *AddHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// TODO: bearer_token needs to be changed in each request cycle as there are many accounts that need to be called
req.Header.Add("Authorization", "Bearer token_here")
req.Header.Add("User-Agent", "go")
return adt.T.RoundTrip(req)
}
func NewAddHeaderTransport(T http.RoundTripper) *AddHeaderTransport {
if T == nil {
T = http.DefaultTransport
}
return &AddHeaderTransport{T}
}
func NewClientWithAccessToken(timeout, tcpConnTimeout, tlsConnTimeout time.Duration) *http.Client {
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: tcpConnTimeout,
}).DialContext,
TLSHandshakeTimeout: tlsConnTimeout,
}
cc := http.Client{
Transport: NewAddHeaderTransport(transport),
Timeout: timeout,
}
return &cc
}
func DefaultClientWithAccessToken() *http.Client {
return NewClientWithAccessToken(
DEFAULT_TIMEOUT_MS*time.Millisecond,
DEFAULT_CONNECTION_TIMEOUT_MS*time.Millisecond,
DEFAULT_CONNECTION_TIMEOUT_MS*time.Millisecond,
)
}
I want to submit a pull request that provides a less opaque error type. This is similar to #29 but has a broader purpose. I want to get some feedback before I submit a pull request.
Can I export the errors type? That will allow me to do something with the Message and Locations.
How do I provide more information about where Client.do() is generating the error? I see 6 locations where an error is returned. Which is a better approach:
a) Should I declare a different error type for each of these conditions? Is that the idiomatic Go way? That appears to be what the golang standard library does.
b) Should I use an enum and wrap the error like this https://play.golang.org/p/xPca1LmOBbp? One advantage of this approach is that (unlike option a) we aren't declaring a new error type that is just some wrapper for another error type (like a json error type). The enum describes where the error occurs not the shape of the error.
c) Something else?
Hi,
Any plan to support Inline Fragments ?
Example :
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid { # <-- Inline Fragments
primaryFunction
}
}
}
Do you think it would be feasible ?
Edit: manage to do without it. But it would be a nice feature :)
Hi,
When calling mutate function with a null variables I have the following error :
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x98 pc=0x8edbc6]
goroutine 21 [running]:
testing.tRunner.func1(0xc4204200f0)
/usr/lib/go-1.10/src/testing/testing.go:742 +0x29d
panic(0x9cf5e0, 0xda4180)
/usr/lib/go-1.10/src/runtime/panic.go:505 +0x229
github.com/shurcooL/graphql.writeArgumentType(0xafc4c0, 0xc42024aee0, 0x0, 0x0, 0xc420446101)
/opt/go/src/github.com/shurcooL/graphql/query.go:58 +0x26
github.com/shurcooL/graphql.queryArguments(0xc4203fec00, 0xc4200e6210, 0xc420178180)
/opt/go/src/github.com/shurcooL/graphql/query.go:46 +0x22b
github.com/shurcooL/graphql.constructMutation(0x9bf0e0, 0xc4200e6210, 0xc4203fec00, 0x4643fc, 0xda4bc0)
/opt/go/src/github.com/shurcooL/graphql/query.go:24 +0x6a
github.com/shurcooL/graphql.(*Client).do(0xc420177f28, 0xb04e20, 0xc4200a4050, 0xc420177b01, 0x9bf0e0, 0xc4200e6210, 0xc4203fec00, 0x0, 0x0)
/opt/go/src/github.com/shurcooL/graphql/graphql.go:53 +0x504
github.com/shurcooL/graphql.(*Client).Mutate(0xc420177f28, 0xb04e20, 0xc4200a4050, 0x9bf0e0, 0xc4200e6210, 0xc4203fec00, 0x0, 0x0)
/opt/go/src/github.com/shurcooL/graphql/graphql.go:43 +0x65
gitlab.com/YBuron/lib-go/models.(*ModelDefinition).CreateRecordGraphql(0xdaba80, 0xc4204200f0, 0xc420177f28, 0xa81ef1, 0x10, 0xa7f83b, 0xb, 0xc420177cd8, 0xc420177c78, 0x412c28)
My receiving struct
type requirementCreateMutation struct {Requirement requirementResult graphql:"createRequirement(title: $title, parentID: $parentID)"}
My variables :
err := client.Mutate(context.Background(), createMutation, map[string]interface{ 'title': 'test', 'parentID': nil, })
My graphql schema :
createRequirement( title: String! parentID: ID)
So it seems the error appears if we try to send a nil value to a non required graphql field. I plan to use different struct if parent is nil as workaround, but imho having a traceback in such case is a bug. Ideally, it should just send a nil value to the graphql server.
Best regards,
So, I'm getting this error message about this ISODateTime type being expected, but I can't see that in this library. How do I fix this?
Thanks!
Currently the only header being set is the content type:
/shurcooL/go/ctxhttp/ctxhttp.go:83
req.Header.Set("Content-Type", bodyType)
It would be helpful to allow setting custom headers in addition to the content-type since some graphql servers use headers for auth tokens and setting other variables.
Thanks
This is an optional future enhancement that we can consider.
If/when we parse the incoming GraphQL query, we can apply the @skip
and @include
GraphQL directives on the client-side. The variables values are known, so there’s no need to send a directive that will evaluate to false anyway. The potential benefit is that outbound query size can be smaller and simpler.
GraphQL directives documentation:
This is a tracking issue; I don’t have plans to work on this soon.
I know this error has popped up before, but previous solutions don't seem to apply to me (or they do and I've overlooked it somehow). I'm using this module to itneract with the Shopify GraphQL API. I have a function like this:
func GetCustomersGql(client *graphql.Client) *[]entity.RawCustomer {
type address struct {
Id string
Address1 string
Address2 string
City string
Company string
Country string
CountryCode string
Province string
ProvinceCode string
Zip string
}
type node struct {
Id string
Email string
FirstName string
LastName string
State string
Note string
VerifiedEmail bool
OrdersCount string
TaxExempt bool
TotalSpent string
Phone string
Tags string
AcceptsMarketing bool
DefaultAddress address
CreatedAt string
UpdatedAt string
}
type edge struct {
Cursor string
Node node
}
var query struct {
Customers struct {
PageInfo struct {
HasNextPage bool
HasPreviousPage bool
}
Edges []edge
} `graphql:"customers(first: 2)"`
}
err := client.Query(context.Background(), &query, nil)
if err != nil {
fmt.Println("GraphQL Problem")
panic(err)
}
fmt.Println(query.Customers)
return nil // for now
}
However, when I run this function, I get the following error: slice doesn't exist in any of 1 places to unmarshal
.
This is the GraphQL call I am trying to perform:
query {
customers(first: 2) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
id
email
firstName
lastName
state
note
verifiedEmail
ordersCount
taxExempt
totalSpent
phone
acceptsMarketing
defaultAddress {
id
address1
address2
city
company
country
countryCode
province
provinceCode
zip
}
createdAt
updatedAt
}
}
}
}
The response looks something like this:
{
"data": {
"customers": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false
},
"edges": [
{
"cursor": "cursorString",
"node": {
"id": "someID",
"email": "someEmail",
"firstName": "First",
"lastName": "Last",
"state": "ENABLED",
"note": null,
"verifiedEmail": true,
"ordersCount": "0",
"taxExempt": false,
"totalSpent": "0.00",
"phone": null,
"acceptsMarketing": true,
"defaultAddress": null,
"createdAt": "2018-10-13T17:00:00Z",
"updatedAt": "2020-03-01T12:00:00Z"
}
},
{
"cursor": "cursorString",
"node": {
"id": "foo",
"email": "bar",
"firstName": "First",
"lastName": "Last",
"state": "ENABLED",
"note": "",
"verifiedEmail": true,
"ordersCount": "5",
"taxExempt": false,
"totalSpent": "650.00",
"phone": null,
"acceptsMarketing": true,
"defaultAddress": {
"id": "someID",
"address1": "some street name",
"address2": "",
"city": "some city",
"company": "",
"country": "United States",
"countryCode": "US",
"province": "California",
"provinceCode": "CA",
"zip": "some zipcode"
},
"createdAt": "2019-10-23T01:47:25Z",
"updatedAt": "2019-11-10T20:38:58Z"
}
}
]
}
},
"extensions": {
"cost": {
"requestedQueryCost": 6,
"actualQueryCost": 6,
"throttleStatus": {
"maximumAvailable": 2000.0,
"currentlyAvailable": 1994,
"restoreRate": 100.0
}
}
}
}
I can't figure out what I'm missing. I have a slice for the edges. And no other slice appears in the response.
To be able to use other transmission protocols, such as WebSockets for example, shurcooL/graphql needs to be abstracted away from the actual transport layer.
This abstraction could also allow:
I suggest to define an interface with a default HTTP client implementation available out of the box and proper documentation for custom implementations.
Code-wise it could look somewhat like this:
func NewClient(transport *TransportInterface, scalars []reflect.Type) *Client
client := graphql.NewClient(graphql.NewHTTPTransport("/query", &http.Client{...}, nil)
I'm a newbie to graphql and fail to query Tibber:
var res struct {
Viewer struct {
Home struct {
ID string
TimeZone string
CurrentSubscription tibber.Subscription
} //`graphql:"home(id: $id)"`
}
}
v := map[string]interface{}{
"id": graphql.ID(t.HomeID),
}
if err := t.client.Query(context.Background(), &res, v); err != nil {
t.log.ERROR.Println(err)
continue
}
Creates this query which looks right:
{"query":"query($id:ID!){viewer{home{id,timeZone,currentSubscription{id,status,priceInfo{current{level,startsAt,total},today{level,startsAt,total}}}}}}","variables":{"id":"d1007ead2dc84a2b82f0de19451c5fb22112f7ae11d19bf2bedb224a003ff74a"}}
But the server errrors:
{"errors":[{"message":"Field \"home\" argument \"id\" of type \"ID!\" is required, but it was not provided.","locations":[{"line":1,"column":23}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}},{"message":"Variable \"$id\" is never used.","locations":[{"line":1,"column":7}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}]}
I would appreciate a hint what's wrong here.
The query and variables as follows:
var query struct {
NameStringsByUuid []struct {
InputId graphql.String `graphql:"inputId"`
} `graphql:"nameStringsByUuid(uuids:[$someid1, $someid2])"`
}
variables := map[string]interface{}{
"someid1": graphql.ID("7db4f8a2-aafe-56b6-8838-89522c67d9f0"),
"someid2": graphql.ID("a5cd7171-03e3-5d36-88a4-8eb921f4d159"),
}
Produces query with errors (no commas between query parameters):
query($someid1:ID!$someid2:ID!){nameStringsByUuid(uuids:[$someid1, $someid2]){inputId}}
On the other hand, when I'm using queries as follows:
var query struct {
NameStringsByUuid []struct {
InputId graphql.String `graphql:"inputId"`
} `graphql:"nameStringsByUuid(uuids:[$someids])"`
}
variables := map[string]interface{}{
"someids": []graphql.ID{
graphql.ID("7db4f8a2-aafe-56b6-8838-89522c67d9f0"),
graphql.ID("a5cd7171-03e3-5d36-88a4-8eb921f4d159"),
},
}
It produces query with errors too (variable type should be $someids:[ID!]!
with bang in the end):
query($someids:[ID!]){nameStringsByUuid(uuids:[$someids]){inputId}}
Is it a bug, or am I doing something wrong?
I see that currently this library requires every query to be a "golang type".
It is not possible to use graphql string (or file) with ready to use query.
I am generating queries in Hasura, so I really don't need to convert them do golang types, I think.
WDYT?
Hi,
All I need is to use your "constructQuery" for tests. Please make it public.
I got custom types at queries and found that I want to write small assert test to avoid bad constructions.
For example, I converted an input map to custom type:
type codes map[string]string
codesInput := make([]codes, 0, len(filterList))
for _, v := range filterList {
codesInput = append(codesInput, codes(v))
}
variables := map[string]interface{}{
"filter": codesInput,
}
It gives me the correct query after construction:
query($filter:[codes!]!){classification(filters: $filter)}
But when I use map[string]string
instead of codes
, it transforms to:
query($filter:[!]!){classification(filters: $filter)}}
So I need to protect "codes" type name by tests.
Operation names are described at http://graphql.org/learn/queries/#operation-name:
The operation name is a meaningful and explicit name for your operation. It can be very useful for debugging and server-side logging reasons. When something goes wrong either in your network logs or your GraphQL server, it is easier to identify a query in your codebase by name instead of trying to decipher the contents. Think of this just like a function name in your favorite programming language. For example, in JavaScript we can easily work only with anonymous functions, but when we give a function a name, it's easier to track it down, debug our code, and log when it's called. In the same way, GraphQL query and mutation names, along with fragment names, can be a useful debugging tool on the server side to identify different GraphQL requests.
Notably, it's most useful to be able to provide operation names for server-side logging and debugging reasons.
We should support this.
I constructed an apollo-server following https://www.apollographql.com/docs/apollo-server/getting-started/
and wrote my own test code like below and ran it.
package main
import (
"context"
"fmt"
"github.com/shurcooL/graphql"
)
func main() {
graphqlEndPoint := "http://localhost:4000/graphql"
client := graphql.NewClient(graphqlEndPoint, nil)
var query struct {
Books struct {
Title graphql.String
Author graphql.String
}
}
err := client.Query(context.Background(), &query, nil)
if err != nil {
fmt.Println("Error!")
fmt.Println(err)
} else {
books := query.Books
title := books.Title
fmt.Println("Book's title: ", title)
}
}
But, the output keeps saying
slice doesn't exist in any of 1 places to unmarshal
At the same time, HTTP request and response were exchanged between the server and my test code normally like,
POST /graphql HTTP/1.1
Host: localhost:4000
User-Agent: Go-http-client/1.1
Content-Length: 34
Content-Type: application/json
Accept-Encoding: gzip
{"query":"{books{title,author}}"}
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Content-Length: 151
ETag: W/"97-OQyi1FTc2fXipdwwic9mDqhbuOo"
Date: Wed, 04 Sep 2019 09:22:20 GMT
Connection: keep-alive
{"data":{"books":[{"title":"Harry Potter and the Chamber of Secrets","author":"J.K. Rowling"},{"title":"Jurassic Park","author":"Michael Crichton"}]}}
Did I do something wrong?
I'm attempting to perform the following query:
var query struct {
Orders struct {
PageInfo struct {
HasNextPage bool
HasPreviousPage bool
}
Edges []struct {
Cursor string
Node struct {
Refunds []struct {
Id string
Note string
CreatedAt string
UpdatedAt string
RefundLineItems struct {
Edges []struct {
Node struct {
LineItem struct {
Id string
Name string
Sku string
}
Quantity int
SubtotalSet struct {
ShopMoney struct {
Amount string
}
}
PriceSet struct {
ShopMoney struct {
Amount string
}
}
}
}
} `graphql:"refundLineItems(first: 20)"`
} `graphql:"refunds(first: 5)"`
}
}
} `graphql:"orders(query: $query, first: 1)"`
}
The value of $query
(at the bottom of the struct) is in the map
date := time.Date(2020, time.January, 1, 0, 0, 0, 0, time.Local).Format("2006-01-02 15:04:05")
variables := map[string]interface{}{
"query": graphql.String(fmt.Sprintf(`\"updated_at:>'%s'\"`, date)),
}
When I perform the query err := client.Query(*ctx, &query, variables)
, it returns no results, which shouldn't be the case. When I directly type in the date for query
`graphql:"orders(query: \"updated_at:>'2020-01-01 00:00:00'\", first: 1)"`
it properly returns the expected result. I compared the string produced with Sprintf with the literal string, and the two are identical. I'm not sure why this could be happening. Ideally I want to be able to use a variable date in the field tag.
I have a very long graphql tag as follows:
`graphql:"nameResolver(names: $names, advancedResolution: $advancedResolution, bestMatchOnly: true, preferredDataSourceIds: [1,12,169])"`
Is there a way to split it to multilines as follows?:
graphql:"nameResolver(names: $names,
advancedResolution: $advancedResolution,
bestMatchOnly: true,
preferredDataSourceIds: [1,12,169])"`
Am i doing something wrong or the substitution of the variable is not accurate?
I get following error , my initialization below
"{\"errors\":[{\"message\":\"Variable \\\"$evar\\\" is never used.\",\"locations\":[{\"line\":1,\"column\":7}],\"extensions\":{\"code\":\"GRAPHQL_VALIDATION_FAILED\"}}],\"data\":null}"
var query struct {
GetBusinessUnits []struct {
Name graphql.String
} `graphql:getBusinessUnits(env:$evar)"`
}
var _ = gauge.Step("Send CLS to rum backend", func() {
client := graphql.NewClient("https://localhost:8080/query", nil)
variables := map[string]interface{}{
"evar": graphql.String("e2e"),
}
err := client.Query(context.Background(), &query, variables)
if err != nil {
fmt.Println(err)
}
fmt.Println(query.GetBusinessUnits[0].Name)
})
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.