...

Text file src/github.com/shurcooL/githubv4/README.md

Documentation: github.com/shurcooL/githubv4

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

View as plain text