Package githubv4 is a client library for accessing GitHub GraphQL API v4 (https://developer.github.com/v4/).

githubv4

Build Status GoDoc

Package githubv4 is a client library for accessing GitHub GraphQL API v4 (https://docs.github.com/en/graphql).

If you're looking for a client library for GitHub REST API v3, the recommended package is github.com/google/go-github/github.

Focus

  • Friendly, simple and powerful API.
  • Correctness, high performance and efficiency.
  • Support all of GitHub GraphQL API v4 via code generation from schema.

Installation

githubv4 requires Go version 1.8 or later.

go get -u github.com/shurcooL/githubv4

Usage

Authentication

GitHub GraphQL API v4 requires authentication. 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 package. You'll need an OAuth token from GitHub (for example, a personal API token) with the right scopes. Then:

import "golang.org/x/oauth2"

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

	client := githubv4.NewClient(httpClient)
	// Use client...
}

If you are using GitHub Enterprise, use githubv4.NewEnterpriseClient:

client := githubv4.NewEnterpriseClient(os.Getenv("GITHUB_ENDPOINT"), httpClient)
// Use client...

Simple Query

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

For example, to make the following GraphQL query:

query {
	viewer {
		login
		createdAt
	}
}

You can define this variable:

var query struct {
	Viewer struct {
		Login     githubv4.String
		CreatedAt githubv4.DateTime
	}
}

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

err := client.Query(context.Background(), &query, nil)
if err != nil {
	// Handle error.
}
fmt.Println("    Login:", query.Viewer.Login)
fmt.Println("CreatedAt:", query.Viewer.CreatedAt)

// Output:
//     Login: gopher
// CreatedAt: 2017-05-26 21:17:14 +0000 UTC

Scalar Types

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

You can use these types when writing queries:

var query struct {
	Viewer struct {
		Login          githubv4.String
		CreatedAt      githubv4.DateTime
		IsBountyHunter githubv4.Boolean
		BioHTML        githubv4.HTML
		WebsiteURL     githubv4.URI
	}
}
// Call client.Query() and use results in query...

However, depending on how you're planning to use the results of your query, it's often more convenient to use other Go types.

The 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 interface is respected.

That means you can simplify the earlier query by using predeclared Go types:

// import "time"

var query struct {
	Viewer struct {
		Login          string    // E.g., "gopher".
		CreatedAt      time.Time // E.g., time.Date(2017, 5, 26, 21, 17, 14, 0, time.UTC).
		IsBountyHunter bool      // E.g., true.
		BioHTML        string    // E.g., `I am learning <a href="https://graphql.org">GraphQL</a>!`.
		WebsiteURL     string    // E.g., "https://golang.org".
	}
}
// Call client.Query() and use results in query...

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

// import "html/template"

type MyBoolean bool

var query struct {
	Viewer struct {
		Login          string        // E.g., "gopher".
		CreatedAt      string        // E.g., "2017-05-26T21:17:14Z".
		IsBountyHunter MyBoolean     // E.g., MyBoolean(true).
		BioHTML        template.HTML // E.g., template.HTML(`I am learning <a href="https://graphql.org">GraphQL</a>!`).
		WebsiteURL     template.URL  // E.g., template.URL("https://golang.org").
	}
}
// Call client.Query() and use results in query...

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:

{
	repository(owner: "octocat", name: "Hello-World") {
		description
	}
}

You can define this variable:

var q struct {
	Repository struct {
		Description string
	} `graphql:"repository(owner: \"octocat\", name: \"Hello-World\")"`
}

Then call client.Query:

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

// Output:
// My first repository on GitHub!

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:

// fetchRepoDescription fetches description of repo with owner and name.
func fetchRepoDescription(ctx context.Context, owner, name string) (string, error) {
	var q struct {
		Repository struct {
			Description string
		} `graphql:"repository(owner: $owner, name: $name)"`
	}

When sending variables to GraphQL, you need to use exact types that match GraphQL scalar types, otherwise the GraphQL server will return an error.

So, define a variables map with their values that are converted to GraphQL scalar types:

	variables := map[string]interface{}{
		"owner": githubv4.String(owner),
		"name":  githubv4.String(name),
	}

Finally, call client.Query providing variables:

	err := client.Query(ctx, &q, variables)
	return q.Repository.Description, err
}

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:

{
	repositoryOwner(login: "github") {
		login
		... on Organization {
			description
		}
		... on User {
			bio
		}
	}
}

You can define this variable:

var q struct {
	RepositoryOwner struct {
		Login        string
		Organization struct {
			Description string
		} `graphql:"... on Organization"`
		User struct {
			Bio string
		} `graphql:"... on User"`
	} `graphql:"repositoryOwner(login: \"github\")"`
}

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

type (
	OrganizationFragment struct {
		Description string
	}
	UserFragment struct {
		Bio string
	}
)

var q struct {
	RepositoryOwner struct {
		Login                string
		OrganizationFragment `graphql:"... on Organization"`
		UserFragment         `graphql:"... on User"`
	} `graphql:"repositoryOwner(login: \"github\")"`
}

Then call client.Query:

err := client.Query(context.Background(), &q, nil)
if err != nil {
	// Handle error.
}
fmt.Println(q.RepositoryOwner.Login)
fmt.Println(q.RepositoryOwner.Description)
fmt.Println(q.RepositoryOwner.Bio)

// Output:
// github
// How people build software.
//

Pagination

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

type comment struct {
	Body   string
	Author struct {
		Login     string
		AvatarURL string `graphql:"avatarUrl(size: 72)"`
	}
	ViewerCanReact bool
}
var q struct {
	Repository struct {
		Issue struct {
			Comments struct {
				Nodes    []comment
				PageInfo struct {
					EndCursor   githubv4.String
					HasNextPage bool
				}
			} `graphql:"comments(first: 100, after: $commentsCursor)"` // 100 per page.
		} `graphql:"issue(number: $issueNumber)"`
	} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
}
variables := map[string]interface{}{
	"repositoryOwner": githubv4.String(owner),
	"repositoryName":  githubv4.String(name),
	"issueNumber":     githubv4.Int(issue),
	"commentsCursor":  (*githubv4.String)(nil), // Null after argument to get first page.
}

// Get comments from all pages.
var allComments []comment
for {
	err := s.clQL.Query(ctx, &q, variables)
	if err != nil {
		return err
	}
	allComments = append(allComments, q.Repository.Issue.Comments.Nodes...)
	if !q.Repository.Issue.Comments.PageInfo.HasNextPage {
		break
	}
	variables["commentsCursor"] = githubv4.NewString(q.Repository.Issue.Comments.PageInfo.EndCursor)
}

There is more than one way to perform pagination. Consider additional fields inside PageInfo object.

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($input: AddReactionInput!) {
	addReaction(input: $input) {
		reaction {
			content
		}
		subject {
			id
		}
	}
}
variables {
	"input": {
		"subjectId": "MDU6SXNzdWUyMTc5NTQ0OTc=",
		"content": "HOORAY"
	}
}

You can define:

var m struct {
	AddReaction struct {
		Reaction struct {
			Content githubv4.ReactionContent
		}
		Subject struct {
			ID githubv4.ID
		}
	} `graphql:"addReaction(input: $input)"`
}
input := githubv4.AddReactionInput{
	SubjectID: targetIssue.ID, // ID of the target issue from a previous query.
	Content:   githubv4.ReactionContentHooray,
}

Then call client.Mutate:

err := client.Mutate(context.Background(), &m, input, nil)
if err != nil {
	// Handle error.
}
fmt.Printf("Added a %v reaction to subject with ID %#v!\n", m.AddReaction.Reaction.Content, m.AddReaction.Subject.ID)

// Output:
// Added a HOORAY reaction to subject with ID "MDU6SXNzdWUyMTc5NTQ0OTc="!

Directories

Path Synopsis
example/githubv4dev githubv4dev is a test program currently being used for developing githubv4 package.

License

Comments
  • Type mismatch between variable and argument for optional String / ID

    Type mismatch between variable and argument for optional String / ID

    In trying to query the GitHub repositoryOwner for retrieving repositories for organizations and/or users, I ran into a problem involving the workaround from https://github.com/shurcooL/githubv4/issues/12 as the interface does not appear to like ID for EndCursor.

    When calling this query, the following error is returned from GitHub GraphQL API:

    Error: Message: Type mismatch on variable $endCursor and argument after (ID / String), Locations: [{Line:1 Column:101}]

    type reposQuery struct {
    	RepositoryOwner struct {
    		Repositories struct {
    			Nodes []struct {
    				Name string
    			}
    			PageInfo struct {
    				HasNextPage bool
    				EndCursor   string
    			}
    		} `graphql:"repositories(first: 100, after: $endCursor, ownerAffiliations: [OWNER])"`
    	} `graphql:"repositoryOwner(login: $owner)"`
    }
    
    func getRepos(owner string, endCursor *string) (*reposQuery, error) {
    	query := new(reposQuery)
    	variables := map[string]interface{}{
    		"owner":     graphql.String(owner),
    		"endCursor": endCursor,
    	}
    
    	err := client.Query("getRepos", query, variables)
    
    	return query, err
    }
    

    In stepping through the code, the error emerges in decoding the response from the server where the query is generated as:

    "query getRepos($endCursor:ID$owner:String!){repositoryOwner(login: $owner){repositories(first: 100, after: $endCursor, ownerAffiliations: [OWNER]){nodes{name},pageInfo{hasNextPage,endCursor}}}}"
    

    Any assistance would be greatly appreciated 🙇

  • Enum value names (avoiding collisions).

    Enum value names (avoiding collisions).

    As discovered in #7, the initial schema I had in mind for the enums will not work, because of name collision between enum values of different types:

    // IssueState represents the possible states of an issue.
    type IssueState string
    
    // The possible states of an issue.
    const (
    	Open   IssueState = "OPEN"   // An issue that is still open.
    	Closed IssueState = "CLOSED" // An issue that has been closed.
    )
    
    // PullRequestState represents the possible states of a pull request.
    type PullRequestState string
    
    // The possible states of a pull request.
    const (
    	Open   PullRequestState = "OPEN"   // A pull request that is still open.
    	Closed PullRequestState = "CLOSED" // A pull request that has been closed without being merged.
    	Merged PullRequestState = "MERGED" // A pull request that has been closed by being merged.
    )
    
    // ProjectState represents state of the project; either 'open' or 'closed'.
    type ProjectState string
    
    // State of the project; either 'open' or 'closed'.
    const (
    	Open   ProjectState = "OPEN"   // The project is open.
    	Closed ProjectState = "CLOSED" // The project is closed.
    )
    
    ...
    
    # github.com/shurcooL/githubql
    ./enum.go:71: CreatedAt redeclared in this block
    	previous declaration at ./enum.go:17
    ./enum.go:111: Open redeclared in this block
    	previous declaration at ./enum.go:8
    ./enum.go:112: Closed redeclared in this block
    	previous declaration at ./enum.go:9
    ./enum.go:120: CreatedAt redeclared in this block
    	previous declaration at ./enum.go:71
    ./enum.go:121: UpdatedAt redeclared in this block
    	previous declaration at ./enum.go:18
    ./enum.go:149: CreatedAt redeclared in this block
    	previous declaration at ./enum.go:120
    ./enum.go:150: UpdatedAt redeclared in this block
    	previous declaration at ./enum.go:121
    ./enum.go:152: Name redeclared in this block
    	previous declaration at ./enum.go:19
    ./enum.go:170: Open redeclared in this block
    	previous declaration at ./enum.go:111
    ./enum.go:171: Closed redeclared in this block
    	previous declaration at ./enum.go:112
    ./enum.go:171: too many errors
    

    Solutions

    These are the solutions that I've got so far and are up for consideration.

    Solution 1

    Prepend the type name in front of the enum value name, to ensure each identifier is unique and collisions are not possible:

     package githubql
     
     // ReactionContent represents emojis that can be attached to Issues, Pull Requests and Comments.
     type ReactionContent string
     
     // Emojis that can be attached to Issues, Pull Requests and Comments.
     const (
    -	ThumbsUp   ReactionContent = "THUMBS_UP"   // Represents the 👍 emoji.
    -	ThumbsDown ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
    -	Laugh      ReactionContent = "LAUGH"       // Represents the 😄 emoji.
    -	Hooray     ReactionContent = "HOORAY"      // Represents the 🎉 emoji.
    -	Confused   ReactionContent = "CONFUSED"    // Represents the 😕 emoji.
    -	Heart      ReactionContent = "HEART"       // Represents the ❤️ emoji.
    +	ReactionContentThumbsUp   ReactionContent = "THUMBS_UP"   // Represents the 👍 emoji.
    +	ReactionContentThumbsDown ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
    +	ReactionContentLaugh      ReactionContent = "LAUGH"       // Represents the 😄 emoji.
    +	ReactionContentHooray     ReactionContent = "HOORAY"      // Represents the 🎉 emoji.
    +	ReactionContentConfused   ReactionContent = "CONFUSED"    // Represents the 😕 emoji.
    +	ReactionContentHeart      ReactionContent = "HEART"       // Represents the ❤️ emoji.
     )
    

    Usage becomes:

    input := githubql.AddReactionInput{
    	SubjectID: q.Repository.Issue.ID,
    	Content:   githubql.ReactionContentThumbsUp,
    }
    

    This is very simple, guaranteed to not have collisions. But the enum value identifiers can become quite verbose, and less readable.

    Solution 2

    This is a minor variation of solution 1. The idea is to make the enum values slightly more readable by separating the type name and enum value by a middot-like character ۰ (U+06F0).

     package githubql
     
     // ReactionContent represents emojis that can be attached to Issues, Pull Requests and Comments.
     type ReactionContent string
     
     // Emojis that can be attached to Issues, Pull Requests and Comments.
     const (
    -	ThumbsUp   ReactionContent = "THUMBS_UP"   // Represents the 👍 emoji.
    -	ThumbsDown ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
    -	Laugh      ReactionContent = "LAUGH"       // Represents the 😄 emoji.
    -	Hooray     ReactionContent = "HOORAY"      // Represents the 🎉 emoji.
    -	Confused   ReactionContent = "CONFUSED"    // Represents the 😕 emoji.
    -	Heart      ReactionContent = "HEART"       // Represents the ❤️ emoji.
    +	ReactionContent۰ThumbsUp   ReactionContent = "THUMBS_UP"   // Represents the 👍 emoji.
    +	ReactionContent۰ThumbsDown ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
    +	ReactionContent۰Laugh      ReactionContent = "LAUGH"       // Represents the 😄 emoji.
    +	ReactionContent۰Hooray     ReactionContent = "HOORAY"      // Represents the 🎉 emoji.
    +	ReactionContent۰Confused   ReactionContent = "CONFUSED"    // Represents the 😕 emoji.
    +	ReactionContent۰Heart      ReactionContent = "HEART"       // Represents the ❤️ emoji.
     )
    

    Usage becomes:

    input := githubql.AddReactionInput{
    	SubjectID: q.Repository.Issue.ID,
    	Content:   githubql.ReactionContent۰ThumbsUp,
    }
    

    The unicode character is not easy to type, but easy to achieve via autocompletion:

    image

    Solution 3

    This is also a variation of solution 1. The idea, suggested to me by @scottmansfield (thanks!), is to use an initialism of the type name rather than the full name. This makes the identifier names shorter, but still has a risk of there being name collisions if two different types with same initialism have same enum values.

     package githubql
     
     // ReactionContent represents emojis that can be attached to Issues, Pull Requests and Comments.
     type ReactionContent string
     
     // Emojis that can be attached to Issues, Pull Requests and Comments.
     const (
    -	ThumbsUp   ReactionContent = "THUMBS_UP"   // Represents the 👍 emoji.
    -	ThumbsDown ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
    -	Laugh      ReactionContent = "LAUGH"       // Represents the 😄 emoji.
    -	Hooray     ReactionContent = "HOORAY"      // Represents the 🎉 emoji.
    -	Confused   ReactionContent = "CONFUSED"    // Represents the 😕 emoji.
    -	Heart      ReactionContent = "HEART"       // Represents the ❤️ emoji.
    +	RCThumbsUp   ReactionContent = "THUMBS_UP"   // Represents the 👍 emoji.
    +	RCThumbsDown ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
    +	RCLaugh      ReactionContent = "LAUGH"       // Represents the 😄 emoji.
    +	RCHooray     ReactionContent = "HOORAY"      // Represents the 🎉 emoji.
    +	RCConfused   ReactionContent = "CONFUSED"    // Represents the 😕 emoji.
    +	RCHeart      ReactionContent = "HEART"       // Represents the ❤️ emoji.
     )
    

    Usage becomes:

    input := githubql.AddReactionInput{
    	SubjectID: q.Repository.Issue.ID,
    	Content:   githubql.RCThumbsUp,
    }
    

    Unfortunately, this approach led to a collision with the existing enums in GitHub GraphQL API:

    const ROFCreatedAt ReactionOrderField = "CREATED_AT" // Allows ordering a list of reactions by when they were created.
    
    const ROFCreatedAt RepositoryOrderField = "CREATED_AT" // Order repositories by creation time.
    
    ./enum.go:149: ROFCreatedAt redeclared in this block
    	previous declaration at ./enum.go:71
    

    It's possible to detect when a collision would occur, and use something more specific that doesn't collide, instead of the initialism. But that introduces complexity, and inconsistency/unpredictability in the naming.

    Solution 4

    This idea is somewhat related to solution 2, but instead of a hard-to-type character, it becomes a normal dot selector. The idea is to create a separate package for each enum type, and define its values in that package. The enum types are still in main githubql package.

    package githubql
     
    // ReactionContent represents emojis that can be attached to Issues, Pull Requests and Comments.
    type ReactionContent string
    
    // Package reactioncontent contains enum values of githubql.ReactionContent type.
    package reactioncontent
    
    import "github.com/shurcooL/githubql"
     
    // Emojis that can be attached to Issues, Pull Requests and Comments.
    const (
    	ThumbsUp   githubql.ReactionContent = "THUMBS_UP"   // Represents the 👍 emoji.
    	ThumbsDown githubql.ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
    	Laugh      githubql.ReactionContent = "LAUGH"       // Represents the 😄 emoji.
    	Hooray     githubql.ReactionContent = "HOORAY"      // Represents the 🎉 emoji.
    	Confused   githubql.ReactionContent = "CONFUSED"    // Represents the 😕 emoji.
    	Heart      githubql.ReactionContent = "HEART"       // Represents the ❤️ emoji.
    )
    

    Usage becomes:

    input := githubql.AddReactionInput{
    	SubjectID: q.Repository.Issue.ID,
    	Content:   reactioncontent.ThumbsUp,
    }
    

    The dot character is easy to type, and the code is very readable once written. But this will require importing many new small packages when using enum values. A tool like goimports will make that significantly easier, but it may still be problematic. Also, the documentation will be split up into multiple small packages, which may be harder to read.

    Conclusion

    All solutions considered so far seem to have certain upsides and downsides. I'm not seeing one solution that is clearly superior to all others. I'm considering going with solution 1 or 2 to begin with, and be open to revisit this decision.

  • Question: Would a single pagination cursor solve GitHub v4 API's woes?

    Question: Would a single pagination cursor solve GitHub v4 API's woes?

    While playing with the GitHub v4 GraphQL API, we quickly ran up against what appears to be a significant limitation: multiple pagination cursors can not be followed in a single query.

    At this point, it is not clear to me if this is a fundamental GraphQL API issue or an issue with the GitHub implementation of the GraphQL API, so I would like to open a discussion here where people with more experience can help clear up any inaccuracies and ideally help propose a solution to GitHub.

    Let's paint a hypothetical picture for discussion (but first note that the GitHub v4 GraphQL API limits each entity response to 100 items).

    Let's say a large company has ~200 orgs each with an average of ~250 repositories and each of those repos has ~300 contributors (and each contributor has "owner", "write" or "read" privileges).

    Let's say I would like to build up a githubql query that answers the question:

    "Give me all contributors (and their privileges) of all repositories of all organizations in my account."

    Obviously, pagination is needed... but the way it is currently implemented, a pagination cursor is provided for each list of contributors, each list of repositories, and each list of organizations. As a result, it is not possible to complete the query by following a single pagination cursor. Furthermore, it is not clear to me that the query can be completed at all due to the ambiguity of specifying a pagination cursor for one list of contributors for one org/repo combo versus the next org/repo combo.

    (I will add an example later, but wanted to keep this as small as possible to highlight the issue with the GitHub v4 GraphQL API.)

    Ideally, since a GraphQL query is naturally a depth-first search (since the full depth of the query is specified up-front), there should be a single pagination cursor that can return the paginated results in depth-first order. (As it currently stands, each list is expanded breadth-first with pagination cursors provided for each list that contains over 100 items.)

    I will work on putting together an example, but in the meantime, please let me know which portions of this need more explanation or if you would prefer that I move this discussion to the GitHub forums instead.

  • Generalize transport using an interface

    Generalize transport using an interface

    The current implementation supports using a http.Client. As discussed in #1, it would be nice if there is a transport interface, so that users of the package can implement their own transport.

    In my case, I am using graphql over GRPC. GRPC is built on top of http.Client and the http.Client is not used directly. Instead, the a GRPC connection is passed to the generated GRPC client. For a simple example see: https://github.com/grpc/grpc-go/blob/master/examples/helloworld/greeter_client/main.go

    I'd imagine that the interface should be quite low level and "close to the wire". In other words, transports should not have to know too much about how graphql works.

  • Generalize the client?

    Generalize the client?

    Going by the examples, it looks like this might be great as a general graphql client that should be able to query any graphql server. Maybe a command line tool can be provided to run an introspection query against a graphql server to generate the appropriate types as well.

  • Question: How to add a querystring as a variable?

    Question: How to add a querystring as a variable?

    Hi,

    Hopefully this is simple miss on my part.

    	var query struct {
    		Search struct {
    			RepositoryCount int
    			PageInfo        struct {
    				EndCursor   githubv4.String
    				HasNextPage bool
    			}
    			Repos []struct {
    				Repository respository `graphql:"... on Repository"`
    			} `graphql:"nodes"`
    		} `graphql:"search(first: 100, after: $repocursor, type: REPOSITORY, query: $querystring)"`
    		RateLimit struct {
    			Cost      githubv4.Int
    			Limit     githubv4.Int
    			Remaining githubv4.Int
    			ResetAt   githubv4.DateTime
    		}
    	}
    
    	variables := map[string]interface{}{
    		"repocursor":  (*githubv4.String)(nil),
    		"querystring": githubv4.String(`\"archived: false pushed:>2020-04-01 created:2020-01-01..2020-02-01\"`),
    	}
    

    What is the correct format/method to substitute in a querystring?

    Thanks in advance

  • Proposal: Rename package to githubv4.

    Proposal: Rename package to githubv4.

    This is something I've been thinking about for the last few months, and I am increasingly convinced this would be an improvement. If accepted, it's better to get this done sooner, before the library has many more users.

    The proposal is:

    -// Package githubql is a client library for accessing GitHub
    +// Package githubv4 is a client library for accessing GitHub
     // GraphQL API v4 (https://developer.github.com/v4/).
     //
     // If you're looking for a client library for GitHub REST API v3,
     // the recommended package is github.com/google/go-github/github.
     //
     // Status: In active early research and development. The API will change when
     // opportunities for improvement are discovered; it is not yet frozen.
     //
     // For now, see README for more details.
    -package githubql // import "github.com/shurcooL/githubql"
    +package githubv4 // import "github.com/shurcooL/githubv4"
    

    First, I think there are some issues with the current githubql name (at least in my mind). It sounds cool, like "GitHub Query Language", but that's not very accurate. If anything, it should've been githubgql for "GitHub GraphQL [API]".

    It might be just me, but having githubql and graphql, I constantly keep mixing up their names, even though I'm well aware of their differences. I guess it's just that both start with "G" and end with "QL", which makes the names harder to differentiate.

    Next, I think that githubv4 is a more practical name for the following reason. Currently, GitHub GraphQL API v4 is far from complete (and seemingly, it will not have feature parity with GitHub REST API v3 for many years). So during this transitional time, it's going to be very common to import both:

    import (
    	...
    	"github.com/google/go-github/github"
    	"github.com/shurcooL/githubql"
    )
    

    github and githubql don't make for great package names in code that uses both. They're hard to tell apart, have different length names, etc.

    So doing this seems favorable:

    import (
    	...
    	githubv3 "github.com/google/go-github/github"
    	githubv4 "github.com/shurcooL/githubql"
    )
    

    Also, in theory, if GitHub were to release GitHub API v5, and it happened to also use GraphQL, that would be another data point showing that githubv4 is a better name than githubql.

    Updating the code and moving/re-fetching the package is a bit annoying for users, but not too difficult or risky.

    According to https://godoc.org/github.com/shurcooL/githubql?importers, there are not very many (public) importers at this time, so this seems viable.

    I'm happy to hear thoughts or convincing arguments on this, if anyone has any. Thanks.

    /cc @willnorris @gmlewis For your awareness and thoughts.

  • "can't decode into non-slice invalid"

    Of the two following queries, the first succeeds and the second spits up an error. I spent a while adding debug printfs in graphql/internal/jsonutil but I wasn't able to figure it out. All I know is that the Commits object in the second query causes the problem. Removing that object and replacing it with something else in the PR works fine.

    Let me know if you need more info :)

    package main
    
    import (
        "context"
        "fmt"
        "github.com/shurcooL/githubql"
        "golang.org/x/oauth2"
        "os"
    )
    
    type repoQuery struct {
        Repository struct {
            PullRequests struct {
                Nodes []struct {
                    Commits struct {
                        Nodes []struct {
                            URL githubql.URI `graphql:"url"`
                        }
                    } `graphql:"commits(last: 1)"`
                }
            } `graphql:"pullRequests(first: 1)"`
        } `graphql:"repository(owner: \"kubernetes\", name: \"test-infra\")"`
    }
    
    type searchQuery struct {
        Search struct {
            Nodes []struct {
                PullRequest struct {
                    Commits struct {
                        Nodes []struct {
                            URL githubql.URI `graphql:"url"`
                        }
                    } `graphql:"commits(last: 1)"`
                } `graphql:"... on PullRequest"`
            }
        } `graphql:"search(type: ISSUE, first: 1, query: \"type:pr repo:kubernetes/test-infra\")"`
    }   
    
    func main() {
        src := oauth2.StaticTokenSource(
            &oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
        )   
        httpClient := oauth2.NewClient(context.Background(), src)
        
        client := githubql.NewClient(httpClient)
        sq := searchQuery{}
        if err := client.Query(context.Background(), &sq, nil); err != nil {
            fmt.Printf("Search error: %v\n", err)
        }
        rq := repoQuery{}
        if err := client.Query(context.Background(), &rq, nil); err != nil {
            fmt.Printf("Repo error: %v\n", err)
        }
    } 
    
    $ GITHUB_TOKEN=<redacted> go run test.go
    Search error: can't decode into non-slice invalid
    
  • Field 'bypassPullRequestActorIds' doesn't exist on type 'BranchProtectionRule'

    Field 'bypassPullRequestActorIds' doesn't exist on type 'BranchProtectionRule'

    I'm getting an error:

    {
     "errors": [
      {
       "path": [
        "query",
        "node",
        "... on BranchProtectionRule",
        "bypassPullRequestActorIds"
       ],
       "extensions": {
        "code": "undefinedField",
        "typeName": "BranchProtectionRule",
        "fieldName": "bypassPullRequestActorIds"
       },
       "locations": [
        {
         "line": 1,
         "column": 635
        }
       ],
       "message": "Field 'bypassPullRequestActorIds' doesn't exist on type 'BranchProtectionRule'"
      }
     ]
    }
    

    when trying to use updated API from your last commit requested via issue #97. I think that they have updated the API again and now there is bypassPullRequestAllowances instead of the bypassPullRequestActorIds as documented here.

    Please could you re-generate your code again?

  • Help in structuring a query

    Help in structuring a query

    I need help/example on how to structure this query

    query rootQuery {
      search(query: "repo:cli/cli is:open type:pr", type: ISSUE, first: 10) {
        nodes {
          ... on PullRequest {
            id
            url
            title
            author {
              login
            }
          }
        }
      }
    }
    
    

    My structure looks like:

    type SearchQuery struct {
    	Search Search `graphql:"search(query: $q, type: $type, first: $first)"`
    }
    
    type Search struct {
    	Nodes []PullRequestFragment `graphql:"... on PullRequest"`
    }
    
    type PullRequestFragment struct {
    	ID        string
    	Title     string
    	Url       string
    	CreatedAt time.Time
    	UpdatedAt time.Time
    	Author    struct {
    		Login string `json:"login"`
    	}
    }
    

    And the errors that I got is Fragment on PullRequest can't be spread inside SearchResultItemConnection

  • Is there a way to use a null type in the after graphql tag?

    Is there a way to use a null type in the after graphql tag?

    I am trying to fetch all the PRs in a repo, I start with this struct to make my first request

    type firstBatchRequest struct {
    		Repository struct {
    			PullRequests struct {
    				Nodes []struct {
    					Author  githubV4Actor
    					Participants struct {
    						Nodes []struct {
    							Login githubv4.String
    						}
    					} `graphql:"participants(first:$nodes)"`
    				}
    				PageInfo struct {
    					EndCursor   githubv4.String
    					HasNextPage githubv4.Boolean
    				}
    			} `graphql:"pullRequests(baseRefName:$branch,first:$prNumber)"`
    		} `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"`
    	}
    

    with these options

    opts := map[string]interface{}{
    			"repositoryOwner": githubv4.String("someone"),
    			"repositoryName":  githubv4.String("something"),
    			"prNumber":        githubv4.Int(100),
    			"branch":          githubv4.String("master"),
    			"nodes":           githubv4.Int(100),
    		}
    

    and once I make the first request I make subsequent requests using the endCursor obtained from the first one to get the PRs after that like this

    type subsequentBatchRequest struct {
    		Repository struct {
    			PullRequests struct {
    				Nodes []struct {
    					Number  githubv4.Int
    					Author  githubV4Actor
    					Participants struct {
    						Nodes []struct {
    							Login githubv4.String
    						}
    					} `graphql:"participants(first:$nodes)"`
    				}
    				PageInfo struct {
    					EndCursor   githubv4.String
    					HasNextPage githubv4.Boolean
    				}
    			} `graphql:"pullRequests(baseRefName:$branch,first:$prNumber,after:$endCursor)"`
    		} `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"`
    	}
    
    opts := map[string]interface{}{
    				"repositoryOwner": githubv4.String("someone"),
    				"repositoryName":  githubv4.String("something"),
    				"prNumber":        githubv4.Int(100),
    				"branch":          githubv4.String("master"),
    				"endCursor":       githubv4.String(cursor),
    				"nodes":           githubv4.Int(100),
    			}
    

    The only difference between these two is that one uses an endcursor and one doesn't, if there was a way to pass a null for the after tag I would be able to reduce the two structs to just one and reuse it, is there anyway to do that?

  • feat: create automation for schema updates

    feat: create automation for schema updates

    This CI job automatically opens a PR when a change happens to the graphql schema to hopefully create a quicker turn around time for updates. I've set it to run nightly or manually (by people who have the necessary permissions) via the github UI. I've done some testing in my fork here where I rolled back the master branch a few commits then cherry picked this change just to verify it works as expected. To be tested is the cron scheduling portion however I don't see that being an issue and will be tested within the next 24 hours once it actually hits 2AM UTC.

    If you want to create an automation label or something I can add that, or make any other tweaks, but I think this would be a nice improvement to the repo.

    Test Cases:

    1. Initial run, which created this PR
    2. Second run to verify that it wouldn't create a duplicate PR which it didn't per the docs
    3. Third run after closing (not merging) the initial PR to make sure it opened a new PR as documented.
    4. Fourth Run after merging the PR created in the third run to verify that no unexpected PRs are created
    5. Fifth Run scheduled run occurred as expected
    6. And another run down the line opened this PR using a scheduled run, https://github.com/test-organization-wwsean08/githubv4-test/pull/3
  • chore: Create go.mod and go.sum files

    chore: Create go.mod and go.sum files

    Resolves #88

    Description

    This creates a go.mod/sum file for githubv4 by doing the following

    1. Renaming the example directory to _example in order to not put a hard requirement on golang.org/x/oauth2 for all packages as all go tools ignore directories and files starting with an underscore or dot according to the last sentence of this section
    2. Move example_test.go into the _example directory to prevent a hard requirement on golang.org/x/oauth2. This could cause a breaking change but seems unlikely given the function has no returns and just does print statements
    3. Create the initial go.mod and go.sum files. They likely could be based on an older version than 1.16, but I figured that was a good place to start.

    The reason I am making the slight file changes to prevent a hard requirement on golang.org/x/auth2 is that this library doesn't actually care about how the end client gets the auth token, so long as it's provided, so this gives users a bit more flexibility and prevents them from taking an unneeded requirement.

    Testing done

    Ran the unit tests and verified nothing broke.

  • Unable to query info for issues in Projects

    Unable to query info for issues in Projects

    Hi. I'm running into a problem where I am unable query titles, ids, etc. for items in projects that are issues. I have not tested with pull requests, but I am able to query info for draft issues.

    For example, the following GraphQL query works fine in the GitHub GraphQL API Explorer and with gh api (filling in a valid ProjectV2Item's node_id).

    query GitHubProjectItemQuery($node_id: ID = "") {
      node(id: $node_id) {
        ... on ProjectV2Item {
          content {
            ... on DraftIssue {
              title
            }
            ... on Issue {
              title
            }
            ... on PullRequest {
              title
            }
          }
        }
      }
    }
    

    This is the equivalent I am using in Go.

    var GitHubProjectItemQuery struct {
      Node struct {
        ProjectV2Item struct {
          Content struct {
            DraftIssue struct {
              Title string
            } `graphql:"... on DraftIssue"`
            Issue struct {
              Title string
            } `graphql:"... on Issue"`
            PullRequest struct {
              Title string
            } `graphql:"... on PullRequest"`
          }
        } `graphql:"... on ProjectV2Item"`
      } `graphql:"node(id: $node_id)"`
    }
    variables := map[string]interface{}{
      "node_id": githubv4.ID(node_id),
    }
    err := githubClient.Query(context.Background(), &GitHubProjectItemQuery, variables)
    

    Oddly, for draft issues, the title appears in GitHubProjectItemQuery.Node.ProjectV2Item.Content.DraftIssue.Title, GitHubProjectItemQuery.Node.ProjectV2Item.Content.Issue.Title, and GitHubProjectItemQuery.Node.ProjectV2Item.Content.PullRequest.Title. For issues, nothing appears for any title, id, etc.

    Any ideas as to what exactly is going on here? Thanks for the help.

  • Created github client is tied to a single access token

    Created github client is tied to a single access token

    Currently if I create a client, the client ties to a token. This isn't ideal for a multi-tenant system where I have to create a new client for each request as the token is gonna be different.

    A better way is to reuse the client for different tokens coming from different tenants.

  • Is it possible to dynamically include graphql fields?

    Is it possible to dynamically include graphql fields?

    Hi all!

    I'm wondering if it's possible to use this library in a way that "dynamically" sets what GraphQL fields are used in a query (in the actual request to the GitHub API).

    By "dynamic" I mean a way of specifying at runtime what fields to include in a request. For my use case, I'd actually be more interested in excluding fields under certain conditions 😃 .

    For context, I maintain this project, which uses this library to implement the GitHub tables. We often hit GitHub's GraphQL API rate limit - which I believe is related to the number of fields/connections we request.

    We have the ability to know, in our SQL query, which columns (GraphQL fields) are actually needed/requested by the user, so if we could only send those in the request to the GitHub API, that would probably allow us to avoid hitting the rate limit as frequently.

    Is this possible today?

Total-go-shopify-graphql - A simple client using the Shopify GraphQL Admin API

A simple client using the Shopify GraphQL Admin API.

Jan 25, 2022
Go library for accessing the GitHub API

go-github go-github is a Go client library for accessing the GitHub API v3. Currently, go-github requires Go version 1.9 or greater. go-github tracks

Dec 30, 2022
NotionGo is a Go client library for accessing the Notion API v1.

NotionGo (WIP) NotionGo is a Go client library for accessing the Notion API v1. Installation NotionGo is compatible with modern Go releases in module

May 22, 2021
go-ftx go-ftx is a Go client library for accessing the FTX API

go-ftx go-ftx is a Go client library for accessing the FTX API

Nov 14, 2022
Go library for accessing the MyAnimeList API: http://myanimelist.net/modules.php?go=api

go-myanimelist go-myanimelist is a Go client library for accessing the MyAnimeList API. Project Status The MyAnimeList API has been stable for years a

Sep 28, 2022
Go library for accessing trending repositories and developers at Github.
Go library for accessing trending repositories and developers at Github.

go-trending A package to retrieve trending repositories and developers from Github written in golang. This package were inspired by rochefort/git-tren

Dec 21, 2022
Go(lang) client library for accessing information of an Apache Mesos cluster.

megos Go(lang) client library for accessing an Apache Mesos cluster. Features Determine the Mesos leader Get the current state of every mesos node (ma

Sep 27, 2022
A GoLang wrapper for Politics & War's API. Forego the hassle of accessing the API directly!

A GoLang wrapper for Politics & War's API. Forego the hassle of accessing the API directly!

Mar 5, 2022
Go library for accessing the Codeship API v2

Codeship API v2 Client for Go Codeship API v2 client for Go. Documentation https://godoc.org/github.com/codeship/codeship-go Usage go get -u github.co

Sep 27, 2022
Go library for accessing the Keycloak API

keycloak keycloak is a Go client library for accessing the Keycloak API. Installation go get github.com/zemirco/keycloak Usage package main import (

Dec 1, 2022
Go library for accessing the BlaBlaCar API

go-blablacar is a Go client library for accessing the BlaBlaCar API.

Nov 27, 2022
🤖🚀📦 A Discord Bot for accessing the cdnjs platform
🤖🚀📦 A Discord Bot for accessing the cdnjs platform

A bridge between https://cdnjs.com/api and Discord Big shoutout to Br1ght0ne for helping me with writing helpers.go/SplitIn

Aug 17, 2022
efsu is for accessing AWS EFS from your machine without a VPN

efsu: VPN-less access to AWS EFS efsu is for accessing AWS EFS from your machine without a VPN. It achieves this by deploying a Lambda function and sh

Mar 11, 2022
Clusterpedia-client - clusterpedia-client supports the use of native client-go mode to call the clusterpedia API

clusterpedia-client supports the use of native client-go mode to call the cluste

Jan 7, 2022
Client-go - Clusterpedia-client supports the use of native client-go mode to call the clusterpedia API

clusterpedia-client supports the use of native client-go mode to call the cluste

Dec 5, 2022
Unofficial Anilist.co GraphQL API wrapper for GoLang.

anilistWrapGo Unofficial Anilist.co GraphQL API wrapper for GoLang. Examples All examples are present as tests in test directory. Below are a few snip

Dec 20, 2022
Api GraphQL com uma única mutation: maxSum

Api GraphQL com uma única mutation: maxSum. Onde, dada uma lista de números, retorna as posições de uma lista de números que possuem a maior soma obtida a partir de uma sub-lista contínua não vazia.

Nov 9, 2021
Torasemi-todo-api - Todo GraphQL Server For Golang

Todo GraphQL Server 概要 とらゼミのハンズオンで使用するGraphQLサーバです 技術仕様 Name Description golang

Jan 3, 2022