README.md

  1githubv4
  2========
  3
  4[![Build Status](https://travis-ci.org/shurcooL/githubv4.svg?branch=master)](https://travis-ci.org/shurcooL/githubv4) [![GoDoc](https://godoc.org/github.com/shurcooL/githubv4?status.svg)](https://godoc.org/github.com/shurcooL/githubv4)
  5
  6Package `githubv4` is a client library for accessing GitHub GraphQL API v4 (https://developer.github.com/v4/).
  7
  8If you're looking for a client library for GitHub REST API v3, the recommended package is [`github.com/google/go-github/github`](https://godoc.org/github.com/google/go-github/github).
  9
 10**Status:** In research and development. The API will change when opportunities for improvement are discovered; it is not yet frozen.
 11
 12Focus
 13-----
 14
 15-	Friendly, simple and powerful API.
 16-	Correctness, high performance and efficiency.
 17-	Support all of GitHub GraphQL API v4 via code generation from schema.
 18
 19Installation
 20------------
 21
 22`githubv4` requires Go version 1.8 or later.
 23
 24```bash
 25go get -u github.com/shurcooL/githubv4
 26```
 27
 28Usage
 29-----
 30
 31### Authentication
 32
 33GitHub GraphQL API v4 [requires authentication](https://developer.github.com/v4/guides/forming-calls/#authenticating-with-graphql). The `githubv4` 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`](https://golang.org/x/oauth2) package. You'll need an OAuth token from GitHub (for example, a [personal API token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)) with the right scopes. Then:
 34
 35```Go
 36import "golang.org/x/oauth2"
 37
 38func main() {
 39	src := oauth2.StaticTokenSource(
 40		&oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
 41	)
 42	httpClient := oauth2.NewClient(context.Background(), src)
 43
 44	client := githubv4.NewClient(httpClient)
 45	// Use client...
 46}
 47```
 48
 49### Simple Query
 50
 51To make a query, you need to define a Go type that corresponds to the GitHub GraphQL schema, and contains the fields you're interested in querying. You can look up the GitHub GraphQL schema at https://developer.github.com/v4/query/.
 52
 53For example, to make the following GraphQL query:
 54
 55```GraphQL
 56query {
 57	viewer {
 58		login
 59		createdAt
 60	}
 61}
 62```
 63
 64You can define this variable:
 65
 66```Go
 67var query struct {
 68	Viewer struct {
 69		Login     githubv4.String
 70		CreatedAt githubv4.DateTime
 71	}
 72}
 73```
 74
 75Then call `client.Query`, passing a pointer to it:
 76
 77```Go
 78err := client.Query(context.Background(), &query, nil)
 79if err != nil {
 80	// Handle error.
 81}
 82fmt.Println("    Login:", query.Viewer.Login)
 83fmt.Println("CreatedAt:", query.Viewer.CreatedAt)
 84
 85// Output:
 86//     Login: gopher
 87// CreatedAt: 2017-05-26 21:17:14 +0000 UTC
 88```
 89
 90### Scalar Types
 91
 92For each scalar in the GitHub GraphQL schema listed at https://developer.github.com/v4/scalar/, there is a corresponding Go type in package `githubv4`.
 93
 94You can use these types when writing queries:
 95
 96```Go
 97var query struct {
 98	Viewer struct {
 99		Login          githubv4.String
100		CreatedAt      githubv4.DateTime
101		IsBountyHunter githubv4.Boolean
102		BioHTML        githubv4.HTML
103		WebsiteURL     githubv4.URI
104	}
105}
106// Call client.Query() and use results in query...
107```
108
109However, depending on how you're planning to use the results of your query, it's often more convenient to use other Go types.
110
111The `encoding/json` rules are used for converting individual JSON-encoded fields from a GraphQL response into Go values. See https://godoc.org/encoding/json#Unmarshal for details. The [`json.Unmarshaler`](https://godoc.org/encoding/json#Unmarshaler) interface is respected.
112
113That means you can simplify the earlier query by using predeclared Go types:
114
115```Go
116// import "time"
117
118var query struct {
119	Viewer struct {
120		Login          string    // E.g., "gopher".
121		CreatedAt      time.Time // E.g., time.Date(2017, 5, 26, 21, 17, 14, 0, time.UTC).
122		IsBountyHunter bool      // E.g., true.
123		BioHTML        string    // E.g., `I am learning <a href="https://graphql.org">GraphQL</a>!`.
124		WebsiteURL     string    // E.g., "https://golang.org".
125	}
126}
127// Call client.Query() and use results in query...
128```
129
130The [`DateTime`](https://developer.github.com/v4/scalar/datetime/) scalar is described as "an ISO-8601 encoded UTC date string". If you wanted to fetch in that form without parsing it into a `time.Time`, you can use the `string` type. For example, this would work:
131
132```Go
133// import "html/template"
134
135type MyBoolean bool
136
137var query struct {
138	Viewer struct {
139		Login          string        // E.g., "gopher".
140		CreatedAt      string        // E.g., "2017-05-26T21:17:14Z".
141		IsBountyHunter MyBoolean     // E.g., MyBoolean(true).
142		BioHTML        template.HTML // E.g., template.HTML(`I am learning <a href="https://graphql.org">GraphQL</a>!`).
143		WebsiteURL     template.URL  // E.g., template.URL("https://golang.org").
144	}
145}
146// Call client.Query() and use results in query...
147```
148
149### Arguments and Variables
150
151Often, you'll want to specify arguments on some fields. You can use the `graphql` struct field tag for this.
152
153For example, to make the following GraphQL query:
154
155```GraphQL
156{
157	repository(owner: "octocat", name: "Hello-World") {
158		description
159	}
160}
161```
162
163You can define this variable:
164
165```Go
166var q struct {
167	Repository struct {
168		Description string
169	} `graphql:"repository(owner: \"octocat\", name: \"Hello-World\")"`
170}
171```
172
173Then call `client.Query`:
174
175```Go
176err := client.Query(context.Background(), &q, nil)
177if err != nil {
178	// Handle error.
179}
180fmt.Println(q.Repository.Description)
181
182// Output:
183// My first repository on GitHub!
184```
185
186However, 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:
187
188```Go
189// fetchRepoDescription fetches description of repo with owner and name.
190func fetchRepoDescription(ctx context.Context, owner, name string) (string, error) {
191	var q struct {
192		Repository struct {
193			Description string
194		} `graphql:"repository(owner: $owner, name: $name)"`
195	}
196```
197
198When sending variables to GraphQL, you need to use exact types that match GraphQL scalar types, otherwise the GraphQL server will return an error.
199
200So, define a `variables` map with their values that are converted to GraphQL scalar types:
201
202```Go
203	variables := map[string]interface{}{
204		"owner": githubv4.String(owner),
205		"name":  githubv4.String(name),
206	}
207```
208
209Finally, call `client.Query` providing `variables`:
210
211```Go
212	err := client.Query(ctx, &q, variables)
213	return q.Repository.Description, err
214}
215```
216
217### Inline Fragments
218
219Some GraphQL queries contain inline fragments. You can use the `graphql` struct field tag to express them.
220
221For example, to make the following GraphQL query:
222
223```GraphQL
224{
225	repositoryOwner(login: "github") {
226		login
227		... on Organization {
228			description
229		}
230		... on User {
231			bio
232		}
233	}
234}
235```
236
237You can define this variable:
238
239```Go
240var q struct {
241	RepositoryOwner struct {
242		Login        string
243		Organization struct {
244			Description string
245		} `graphql:"... on Organization"`
246		User struct {
247			Bio string
248		} `graphql:"... on User"`
249	} `graphql:"repositoryOwner(login: \"github\")"`
250}
251```
252
253Alternatively, you can define the struct types corresponding to inline fragments, and use them as embedded fields in your query:
254
255```Go
256type (
257	OrganizationFragment struct {
258		Description string
259	}
260	UserFragment struct {
261		Bio string
262	}
263)
264
265var q struct {
266	RepositoryOwner struct {
267		Login                string
268		OrganizationFragment `graphql:"... on Organization"`
269		UserFragment         `graphql:"... on User"`
270	} `graphql:"repositoryOwner(login: \"github\")"`
271}
272```
273
274Then call `client.Query`:
275
276```Go
277err := client.Query(context.Background(), &q, nil)
278if err != nil {
279	// Handle error.
280}
281fmt.Println(q.RepositoryOwner.Login)
282fmt.Println(q.RepositoryOwner.Description)
283fmt.Println(q.RepositoryOwner.Bio)
284
285// Output:
286// github
287// How people build software.
288//
289```
290
291### Pagination
292
293Imagine you wanted to get a complete list of comments in an issue, and not just the first 10 or so. To do that, you'll need to perform multiple queries and use pagination information. For example:
294
295```Go
296type comment struct {
297	Body   string
298	Author struct {
299		Login     string
300		AvatarURL string `graphql:"avatarUrl(size: 72)"`
301	}
302	ViewerCanReact bool
303}
304var q struct {
305	Repository struct {
306		Issue struct {
307			Comments struct {
308				Nodes    []comment
309				PageInfo struct {
310					EndCursor   githubv4.String
311					HasNextPage bool
312				}
313			} `graphql:"comments(first: 100, after: $commentsCursor)"` // 100 per page.
314		} `graphql:"issue(number: $issueNumber)"`
315	} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
316}
317variables := map[string]interface{}{
318	"repositoryOwner": githubv4.String(owner),
319	"repositoryName":  githubv4.String(name),
320	"issueNumber":     githubv4.Int(issue),
321	"commentsCursor":  (*githubv4.String)(nil), // Null after argument to get first page.
322}
323
324// Get comments from all pages.
325var allComments []comment
326for {
327	err := s.clQL.Query(ctx, &q, variables)
328	if err != nil {
329		return err
330	}
331	allComments = append(allComments, q.Repository.Issue.Comments.Nodes...)
332	if !q.Repository.Issue.Comments.PageInfo.HasNextPage {
333		break
334	}
335	variables["commentsCursor"] = githubv4.NewString(q.Repository.Issue.Comments.PageInfo.EndCursor)
336}
337```
338
339There is more than one way to perform pagination. Consider additional fields inside [`PageInfo`](https://developer.github.com/v4/object/pageinfo/) object.
340
341### Mutations
342
343Mutations often require information that you can only find out by performing a query first. Let's suppose you've already done that.
344
345For example, to make the following GraphQL mutation:
346
347```GraphQL
348mutation($input: AddReactionInput!) {
349	addReaction(input: $input) {
350		reaction {
351			content
352		}
353		subject {
354			id
355		}
356	}
357}
358variables {
359	"input": {
360		"subjectId": "MDU6SXNzdWUyMTc5NTQ0OTc=",
361		"content": "HOORAY"
362	}
363}
364```
365
366You can define:
367
368```Go
369var m struct {
370	AddReaction struct {
371		Reaction struct {
372			Content githubv4.ReactionContent
373		}
374		Subject struct {
375			ID githubv4.ID
376		}
377	} `graphql:"addReaction(input: $input)"`
378}
379input := githubv4.AddReactionInput{
380	SubjectID: targetIssue.ID, // ID of the target issue from a previous query.
381	Content:   githubv4.ReactionContentHooray,
382}
383```
384
385Then call `client.Mutate`:
386
387```Go
388err := client.Mutate(context.Background(), &m, input, nil)
389if err != nil {
390	// Handle error.
391}
392fmt.Printf("Added a %v reaction to subject with ID %#v!\n", m.AddReaction.Reaction.Content, m.AddReaction.Subject.ID)
393
394// Output:
395// Added a HOORAY reaction to subject with ID "MDU6SXNzdWUyMTc5NTQ0OTc="!
396```
397
398Directories
399-----------
400
401| Path                                                                                      | Synopsis                                                                            |
402|-------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
403| [example/githubv4dev](https://godoc.org/github.com/shurcooL/githubv4/example/githubv4dev) | githubv4dev is a test program currently being used for developing githubv4 package. |
404
405License
406-------
407
408-	[MIT License](LICENSE)