Giter VIP home page Giter VIP logo

graphql's Introduction

graphql

Go Reference

Package graphql provides a GraphQL client implementation.

For more information, see package github.com/shurcooL/githubv4, which is a specialized version targeting GitHub GraphQL API v4. That package is driving the feature development.

Installation

go get github.com/shurcooL/graphql

Usage

Construct a GraphQL client, specifying the GraphQL server URL. Then, you can use it to make GraphQL queries and mutations.

client := graphql.NewClient("https://example.com/graphql", nil)
// Use client...

Authentication

Some GraphQL servers may require authentication. The graphql package does not directly handle authentication. Instead, when creating a new client, you're expected to pass an http.Client that performs authentication. The easiest and recommended way to do this is to use the golang.org/x/oauth2 package. You'll need an OAuth token with the right scopes. Then:

import "golang.org/x/oauth2"

func main() {
	src := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: os.Getenv("GRAPHQL_TOKEN")},
	)
	httpClient := oauth2.NewClient(context.Background(), src)

	client := graphql.NewClient("https://example.com/graphql", httpClient)
	// Use client...

Simple Query

To make a GraphQL query, you need to define a corresponding Go type.

For example, to make the following GraphQL query:

query {
	me {
		name
	}
}

You can define this variable:

var query struct {
	Me struct {
		Name graphql.String
	}
}

Then call client.Query, passing a pointer to it:

err := client.Query(context.Background(), &query, nil)
if err != nil {
	// Handle error.
}
fmt.Println(query.Me.Name)

// Output: Luke Skywalker

Arguments and Variables

Often, you'll want to specify arguments on some fields. You can use the graphql struct field tag for this.

For example, to make the following GraphQL query:

{
	human(id: "1000") {
		name
		height(unit: METER)
	}
}

You can define this variable:

var q struct {
	Human struct {
		Name   graphql.String
		Height graphql.Float `graphql:"height(unit: METER)"`
	} `graphql:"human(id: \"1000\")"`
}

Then call client.Query:

err := client.Query(context.Background(), &q, nil)
if err != nil {
	// Handle error.
}
fmt.Println(q.Human.Name)
fmt.Println(q.Human.Height)

// Output:
// Luke Skywalker
// 1.72

However, that'll only work if the arguments are constant and known in advance. Otherwise, you will need to make use of variables. Replace the constants in the struct field tag with variable names:

var q struct {
	Human struct {
		Name   graphql.String
		Height graphql.Float `graphql:"height(unit: $unit)"`
	} `graphql:"human(id: $id)"`
}

Then, define a variables map with their values:

variables := map[string]any{
	"id":   graphql.ID(id),
	"unit": starwars.LengthUnit("METER"),
}

Finally, call client.Query providing variables:

err := client.Query(context.Background(), &q, variables)
if err != nil {
	// Handle error.
}

Inline Fragments

Some GraphQL queries contain inline fragments. You can use the graphql struct field tag to express them.

For example, to make the following GraphQL query:

{
	hero(episode: "JEDI") {
		name
		... on Droid {
			primaryFunction
		}
		... on Human {
			height
		}
	}
}

You can define this variable:

var q struct {
	Hero struct {
		Name  graphql.String
		Droid struct {
			PrimaryFunction graphql.String
		} `graphql:"... on Droid"`
		Human struct {
			Height graphql.Float
		} `graphql:"... on Human"`
	} `graphql:"hero(episode: \"JEDI\")"`
}

Alternatively, you can define the struct types corresponding to inline fragments, and use them as embedded fields in your query:

type (
	DroidFragment struct {
		PrimaryFunction graphql.String
	}
	HumanFragment struct {
		Height graphql.Float
	}
)

var q struct {
	Hero struct {
		Name          graphql.String
		DroidFragment `graphql:"... on Droid"`
		HumanFragment `graphql:"... on Human"`
	} `graphql:"hero(episode: \"JEDI\")"`
}

Then call client.Query:

err := client.Query(context.Background(), &q, nil)
if err != nil {
	// Handle error.
}
fmt.Println(q.Hero.Name)
fmt.Println(q.Hero.PrimaryFunction)
fmt.Println(q.Hero.Height)

// Output:
// R2-D2
// Astromech
// 0

Mutations

Mutations often require information that you can only find out by performing a query first. Let's suppose you've already done that.

For example, to make the following GraphQL mutation:

mutation($ep: Episode!, $review: ReviewInput!) {
	createReview(episode: $ep, review: $review) {
		stars
		commentary
	}
}
variables {
	"ep": "JEDI",
	"review": {
		"stars": 5,
		"commentary": "This is a great movie!"
	}
}

You can define:

var m struct {
	CreateReview struct {
		Stars      graphql.Int
		Commentary graphql.String
	} `graphql:"createReview(episode: $ep, review: $review)"`
}
variables := map[string]any{
	"ep": starwars.Episode("JEDI"),
	"review": starwars.ReviewInput{
		Stars:      graphql.Int(5),
		Commentary: graphql.String("This is a great movie!"),
	},
}

Then call client.Mutate:

err := client.Mutate(context.Background(), &m, variables)
if err != nil {
	// Handle error.
}
fmt.Printf("Created a %v star review: %v\n", m.CreateReview.Stars, m.CreateReview.Commentary)

// Output:
// Created a 5 star review: This is a great movie!

Directories

Path Synopsis
ident Package ident provides functions for parsing and converting identifier names between various naming convention.
internal/jsonutil Package jsonutil provides a function for decoding JSON into a GraphQL query data structure.

License

graphql's People

Contributors

alexandear avatar david-bain avatar dmitshur avatar shuheiktgw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql's Issues

Using alternative rest clients

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!

Issue when using a null parameter

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,

"Variable \\\"$evar\\\" is never used.

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)
})

How to handle a mutation that returns a value?

How would you code the following mutation that returns an ID upon creation:

mutation CreateTeamPipeline {
  teamPipelineCreate(input: {teamID: "...", pipelineID: "...", accessLevel: READ_ONLY}) {
    teamPipeline {
      id
    }
  }
}

Ability to add custom headers to the request

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

Nested struct tag not applied to query

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.

Support for generating (limited) recursive queries

The setup

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...

(not-so-great) Workarounds

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.

Potential Solutions

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?

Struct Field Tag not Properly Working

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.

FaunaDB graphql _id defined as _ID not exported

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 ?

Error while generating graphql query with variables

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?

Expecting type ISODateTime

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!

slice doesn't exist in any of 1 places to unmarshal

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.

GraphQL Errors not be returned due to bad unmarshal of Response Data

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
}

Help: Can you dynamically construct a graphql query?

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?

Question: Mutate

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)
}

Include response body in 'unexpected status' error message.

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:

graphql/graphql.go

Lines 72 to 74 in 3658993

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %v", resp.Status)
}

Adding a best effort 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.

Multiline graphql 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])"`

Stack overflow when types contain a cycle

(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.

query with complex arguments are not working

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.

Allow building mutations from maps instead of structs

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

RFC: less opaque error type

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.

  1. Can I export the errors type? That will allow me to do something with the Message and Locations.

  2. 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?

Support specifying operation names.

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.

Expose constructQuery to public function

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.

OAuth2 Bearer Token with Customer HttpClient

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,
	)
}

How to insert an object using mutation

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

Question: Example of doing pagination (especially on nested resources that are paginated)

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!

Help: Keeps saying "slice doesn't exist in any of 1 places to unmarshal

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?

Perform simple GraphQL directives (@skip, @include) on client-side.

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.

Question: mixed types in array

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?

Help: Unable to unmarshal server reply containing a map into `map[string]interface{}`

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

graphqlClient request log

Do you like add request log on graphql client request

like:

{"level": "INFO", "query":"{"query":"queryQl","mutation":"mutationQL"}", "variables":{"args":"args"}}

Please make "constructQuery" exported

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.

Transport layer abstraction

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:

  • subscriptions (which ideally need WebSockets for bi-directional communication)
  • request hijacking / proxying
  • internal mocking of the GraphQL endpoint
  • authentication middleware
  • and possibly even more...

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)

Using unexported field in query results in panic.

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

support JSON scalar type

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?

Support uuid type

What

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.

Fragments Cannot Be Unmarshaled

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.

Structs used in variables need json tags: not intuitive and not documented.

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).

Support for Inline Fragments

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 :)

Mutation struct variable empty?

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
	}

I love this library but it seems unmaintained?

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.

  • Fork this library and start having my organization maintain it ourself
  • Contribute back if the powers that be would actually approve and merge the PRs (which it seems like thats already not the case given 16 open PRs and no new commits since 2018)

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:

  • named queries
  • better HTTP client/transport that supports retry and backoff
  • verbose logging of query and response for easier debugging
  • support for subscription
  • native support for easily adding extra headers (functional options)
  • batching multiple queries
  • removal of needing to use base types - https://github.com/shurcooL/graphql/blob/master/scalar.go
  • built in schema -> struct generator to help with quickstarting a client
  • add go.mod / go.sum
  • turning "question" issues answers into documentation
  • more examples of auth options, ways to handle usecases, and a list completed clients

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 :(

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.