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

Overview

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

Issues
  • 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 🙇

    question 
    opened by andyfeller 12
  • 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.

    API decision 
    opened by dmitshur 8
  • 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.

    question 
    opened by gmlewis 7
  • 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.

    enhancement future 
    opened by F21 6
  • 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 
    opened by F21 6
  • 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

    question 
    opened by superbeeny 4
  • 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.

    API decision NeedsFix 
    opened by dmitshur 4
  • "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
    
    bug 
    opened by spxtr 4
  • 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?

    opened by jtyr 3
  • 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

    question 
    opened by carlqt 3
  • 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?

    opened by palash25 3
  • 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.

    opened by jamesyli 0
  • 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?

    question 
    opened by patrickdevivo 1
  • Test with docker - make test

    Test with docker - make test

    Any new contributor can now clone the repo and run make test to test the source code of the repository.

    Add a docker-compose.yaml and a Makefile to specify the new test target.

    Generate go.mod and go.sum by running:

    • go mod init
    • go mod tidy
    opened by YuviGold 1
  • Support marshalling a null URI

    Support marshalling a null URI

    Currently, in case trying to marshal a null URI struct, it tries to call u.String() function and panics. The marshal behavior should match the unmarshal behavior and return a null bytes array.

    Before changing the scalar.go module, the new test scenario of a nil URI would panic

    Stacktrace (click to expand)
    --- FAIL: TestURI_MarshalJSON (0.00s)
    panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    	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=0x8 pc=0x5c948a]
    
    goroutine 12 [running]:
    testing.tRunner.func1.2(0x6f4160, 0x937ed0)
    	/usr/lib/golang/src/testing/testing.go:1143 +0x332
    testing.tRunner.func1(0xc00016a600)
    	/usr/lib/golang/src/testing/testing.go:1146 +0x4b6
    panic(0x6f4160, 0x937ed0)
    	/usr/lib/golang/src/runtime/panic.go:965 +0x1b9
    encoding/json.(*encodeState).marshal.func1(0xc00005fde8)
    	/usr/lib/golang/src/encoding/json/encode.go:328 +0x8d
    panic(0x6f4160, 0x937ed0)
    	/usr/lib/golang/src/runtime/panic.go:965 +0x1b9
    net/url.(*URL).String(0x0, 0xc00005fc28, 0x40d765)
    	/usr/lib/golang/src/net/url/url.go:813 +0x4a
    github.com/shurcooL/githubv4.URI.MarshalJSON(0x0, 0x734de0, 0x0, 0x7f9358c93a18, 0x0, 0x1)
    	/home/yuvalg/work/github/githubv4/scalar.go:80 +0x2f
    encoding/json.marshalerEncoder(0xc00014e080, 0x734de0, 0x0, 0x19, 0xc000000100)
    	/usr/lib/golang/src/encoding/json/encode.go:479 +0xad
    encoding/json.condAddrEncoder.encode(0x760d40, 0x760d98, 0xc00014e080, 0x734de0, 0x0, 0x19, 0x730100)
    	/usr/lib/golang/src/encoding/json/encode.go:961 +0xb2
    encoding/json.(*encodeState).reflectValue(0xc00014e080, 0x734de0, 0x0, 0x19, 0xc000040100)
    	/usr/lib/golang/src/encoding/json/encode.go:360 +0x82
    encoding/json.(*encodeState).marshal(0xc00014e080, 0x734de0, 0x0, 0xc000150100, 0x0, 0x0)
    	/usr/lib/golang/src/encoding/json/encode.go:332 +0xf9
    encoding/json.Marshal(0x734de0, 0x0, 0x7ed6e5, 0x26, 0x438, 0x4f4f60, 0x8cc078)
    	/usr/lib/golang/src/encoding/json/encode.go:161 +0x52
    github.com/shurcooL/githubv4_test.TestURI_MarshalJSON(0xc00016a600)
    	/home/yuvalg/work/github/githubv4/scalar_test.go:30 +0x158
    testing.tRunner(0xc00016a600, 0x760f98)
    	/usr/lib/golang/src/testing/testing.go:1193 +0xef
    created by testing.(*T).Run
    	/usr/lib/golang/src/testing/testing.go:1238 +0x2b3
    FAIL	github.com/shurcooL/githubv4	0.005s
    ?   	github.com/shurcooL/githubv4/example/githubv4dev	[no test files]
    
    === Failed
    === FAIL: . TestURI_MarshalJSON (0.00s)
    
    opened by YuviGold 1
  • PR to add go.mod has not been merged

    PR to add go.mod has not been merged

    The PR to add go.mod ( #69 ) has been approved. However, it seems to have been left unmerged. It seems that the PR to add go.mod in shurcooL/graphql, which is a dependency of this, is approved but is not merged as well.

    What is the current status of these?

    opened by pddg 0
  • QueryString with Search creating issue

    QueryString with Search creating issue

    I have my code for fetching the information from repo based on programming language

    func generateDataGithub(githubToken string, githubURL string, org string, language string, count int) {
    src := oauth2.StaticTokenSource(
    		&oauth2.Token{AccessToken: githubToken},
    	)
    	httpClient := oauth2.NewClient(context.Background(), src)
    
    	client := githubv4.NewEnterpriseClient(githubURL, httpClient)
    
    var query struct {
    		Search struct {
    			RepositoryCount githubv4.Int
    			Edges []struct {
    				Node struct {
    					Repository struct {
    						Name          githubv4.String
    						NameWithOwner githubv4.String
    						Owner         struct {
    							Login githubv4.String
    						}
    						PrimaryLanguage struct {
    							Name githubv4.String
    						}
    						DefaultBranchRef struct {
    							Name githubv4.String
    						}
    					} `graphql:"... on Repository"`
    				}
    			}
    		}`graphql:"search(first: $count, query: $searchQuery, type: REPOSITORY)"`
    	}
    
    variables := map[string]interface{}{
    		"searchQuery": githubv4.String(fmt.Sprintf(`user:%s language:%s`,githubv4.String(org),githubv4.String(language))),
    		"count":    githubv4.Int(count),
    	}
    

    Executing them but not getting any result back

    err := client.Query(context.Background(), &query, variables)
    	if err != nil {
    		Error.Println(err)
    	}
    

    See Below

    {
            "Search": {
                    "RepositoryCount": 0,
                    "Edges": []
            }
    }
    

    While from Graphql

    {
      search(query: "user:<your org> language:java", type: REPOSITORY, first: 2) {
        repositoryCount
        edges {
          node {
            ... on Repository {
              owner {
                login
              }
              name
              nameWithOwner
              owner {
                login
              }
              primaryLanguage {
                name
              }
              defaultBranchRef {
                name
              }
            }
          }
        }
      }
    }
    
    

    I am getting this

    {
      "data": {
        "search": {
          "repositoryCount": 10,
          "edges": [
            {
              "node": {
                "owner": {
                  "login": "***"
                },
                "name": "mule",
                "nameWithOwner": "***/**",
                "primaryLanguage": {
                  "name": "Java"
                },
                "defaultBranchRef": {
                  "name": "master"
                }
              }
            }
          ]
        }
      }
    }
    

    Any help, as I have seen most of the closed tickets, and implemented what has been suggested.

    WaitingForInfo 
    opened by sumitnagal 2
Releases(2.0.6)
Total-go-shopify-graphql - A simple client using the Shopify GraphQL Admin API

A simple client using the Shopify GraphQL Admin API.

null 0 Jan 25, 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

Ben 4 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

Clouding 6 Jun 29, 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

Google 8.7k Aug 11, 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

Stratos Neiros 31 May 10, 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

Andy Grunwald 54 Jul 18, 2021
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

Andy Grunwald 121 Aug 2, 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!

null 1 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

CodeShip 18 Nov 15, 2021
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 (

Mirco Zeiss 24 Jul 15, 2022
Go library for accessing the BlaBlaCar API

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

Midneit 3 Jul 27, 2021
🤖🚀📦 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

Ilya Revenko 16 Nov 22, 2021
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

Glass Echidna 42 Mar 11, 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

ダンクデル (Sayan Biswas) 14 Feb 17, 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.

Davi Andrade Pontes 1 Nov 9, 2021
Torasemi-todo-api - Todo GraphQL Server For Golang

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

Yusuke Mabuchi 0 Jan 3, 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

Calvin Chen 4 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

clusterpedia.io 7 Jun 17, 2022