shurcooL/githubv4

Type mismatch between variable and argument for optional String / ID

andyfeller opened this issue ยท 12 comments

In trying to query the GitHub repositoryOwner for retrieving repositories for organizations and/or users, I ran into a problem involving the workaround from #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 ๐Ÿ™‡

For reference, this query works with the interface mentioned:

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

If you look at GitHub's RepositoryOwner interface, the after argument of the repositories field has type String, so that's what needs to be provided. Your code can look something like:

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

See here for an example of similar code.

Thanks for the follow up, @dmitshur ! That is what I initially did, however because the endpoint wants endCursor to be optional, I had to change it to a pointer. Does that make sense?

Yes, if it's an optional GraphQL type, using a Go pointer makes sense. That is also what's done in the code example I linked above. The NewString helper exists for convenience of doing this.

So, this is where we're coming full circle as when I'm providing the pointer, GitHub is responding back with the following error:

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

with the underlying generated query being:

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

The only thing I can guess is that the repositoryOwner interface does not like $endCursor:ID with however the variable is being sent over.

From what you've shared, I'm fairly confident the problem is with the variables map you're providing to the Query call, specifically this code:

query := new(reposQuery)
variables := map[string]interface{}{
	"owner":     graphql.String(owner),
	"endCursor": endCursor,
}

err := client.Query("getRepos", query, variables)

I think changing it to the following should work:

query := new(reposQuery)
variables := map[string]interface{}{
	"owner":     graphql.String(owner),
	"endCursor": graphql.NewString(graphql.String(endCursor)),
}

err := client.Query("getRepos", query, variables)

If that doesn't help, can you post a complete program that reproduces the issue for you?

Finally, take a look at https://github.com/shurcooL/githubv4#pagination if you haven't already, since that is a functional example for pagination.

I'll give it a go! (pun not intended) Seriously: thank you for your amazing patience and help โค๏ธ

"endCursor": graphql.NewString(graphql.String(endCursor)),

Unfortunately, this doesn't really solve it especially as it gives compiler errors:

cannot convert endCursor (variable of type *string) to graphql.String compiler InvalidConversion

I've made my repo public and the code is here: https://github.com/andyfeller/gh-dependency-report/blob/initial/cmd/root.go#L294

I'm simply trying to run this as:

go run main.go andyfeller 

Ah, that snippet assumed that endCursor was of type string, but in your code its type is *string. In that case, you can just convert it to a *graphql.String directly:

func getRepos(owner string, endCursor *string) (*reposQuery, error) {
	query := new(reposQuery)
	variables := map[string]interface{}{
		"owner":     graphql.String(owner),
		"endCursor": (*graphql.String)(endCursor),
	}

	err := client.Query("getRepos", query, variables)

	return query, err
}

With that change, the program compiles and getRepos makes a successful query with repository names populated. (There are other parts of that program that need modification to run without errors, for example repos = make([]string, 100) creates a slice with 100 empty repository names; it should probably be repos = make([]string, 0, 100) to create an empty slice with 100 capacity.)

Thank you again for your patience, @dmitshur ๐Ÿ™‡

Would you welcome a PR including a section providing an example of this to avoid confusion from other users?

With that change, the program compiles and getRepos makes a successful query with repository names populated. (There are other parts of that program that need modification to run without errors, for example repos = make([]string, 100) creates a slice with 100 empty repository names; it should probably be repos = make([]string, 0, 100) to create an empty slice with 100 capacity.)

๐Ÿ˜… thanks for the additional advice; it's been a while since my undergraduate days of only doing C development and this is the 3rd time I've tried teaching myself GoLang.

Would you welcome a PR including a section providing an example of this to avoid confusion from other users?

I believe this should be covered by https://github.com/shurcooL/githubv4#pagination; can you please let me know if you think something is missing from there?

I'll close this since I understand the question is resolved.