shurcooL / githubv4

Package githubv4 is a client library for accessing GitHub GraphQL API v4 (https://docs.github.com/en/graphql).
MIT License
1.11k stars 89 forks source link

Decode struct within a slice #30

Open nitsanfiverr opened 6 years ago

nitsanfiverr commented 6 years ago

Hi, I was just using the package to query tags list on my repo. the query looks like this:

{
  repository(owner: "rails", name: "rails") {
    name
    tags: refs(refPrefix: "refs/tags/", first: 2, orderBy: {field: TAG_COMMIT_DATE, direction: DESC}) {
      edges {
        tag: node {
          name
        }
      }
    }
  }
}

The response looks like this:

{
  "data": {
    "repository": {
      "name": "rails",
      "tags": {
        "edges": [
          {
            "tag": {
              "name": "v5.2.0.rc2"
            }
          },
          {
            "tag": {
              "name": "v5.1.5"
            }
          }
        ]
      }
    }
  }
}

it's working fine on graphQL explorer, but fails to decode here in the package. from the small Debugging I did it seems like there is no attempt to decode objects within the slice and I'm getting slice doesn't exist in any of 1 places to unmarshal Any idea or fix ? Thanks !

dmitshur commented 6 years ago

Thanks for reporting.

Can you share the Go code you wrote for that query to help me investigate this?

alexdor commented 6 years ago

For anyone else facing this issue, double check that all of your struct fields are matching the GraphQL response and that you aren't forgetting any slices

maxramqvist commented 6 years ago

Heey, I'm facing a similar issue (Full disclosure: Total newbie to Golang and generally programming, really). It tried some debugging in your graphql and githubv4, and it seems that the request done in GraphiQL and your libraries differ somehow. The response doesn't get populated with the releases, but the releases are there in Github and I get them when I run the request in GraphQL API Explorer (dumped with debugginging and parsed into browser).

My code is a programming excercise for myself:


import (
    "context"
    "fmt"
    "os"
    "strconv"

    "github.com/olekukonko/tablewriter"

    "github.com/shurcooL/githubv4"
    "golang.org/x/oauth2"
)

// Define the structure of the GraphQL request
type node struct {
    Name githubv4.String
}

type release struct {
    Nodes []node
}

type repository struct {
    Name       string
    IsArchived bool
    Releases   struct {
        release
    } `graphql:"releases(last:1, orderBy: {field: CREATED_AT, direction: ASC})"`
}

var q struct {
    Organization struct {
        Name         string
        Repositories struct {
            TotalCount int
            PageInfo   struct {
                EndCursor   githubv4.String
                HasNextPage bool
            }
            Nodes []repository
        } `graphql:"repositories(first: 100, after: $pageCursor)"`
    } `graphql:"organization(login: \"Hashicorp\")"`
}

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

    var allRepos []repository

    variables := map[string]interface{}{
        "pageCursor": (*githubv4.String)(nil), // Null after argument to get first page.
    }
    for { // we'll have to loop through the request as the response will be paginated, at 100 nodes per page.
        err := client.Query(context.Background(), &q, variables)

        if err != nil {
            // Handle error.
            fmt.Println(err)
        }
        allRepos = append(allRepos, q.Organization.Repositories.Nodes...)

        if !q.Organization.Repositories.PageInfo.HasNextPage {
            break
        }
        // this cursor points to the last node returned in every request, so well use that to get the start of the next request
        variables["pageCursor"] = githubv4.NewString(q.Organization.Repositories.PageInfo.EndCursor)
    }

    // Create and render the table
    table := tablewriter.NewWriter(os.Stdout)
    table.SetHeader([]string{"Repositories", "Last release", "Archived"})
    for _, node := range allRepos {
        if len(node.Releases.Nodes) > 0 { // most repos doesn't have releases (nodes in this case), so release.Name will be undefined = solve with if-else
            for _, release := range node.Releases.Nodes {
                table.Append([]string{node.Name, string(release.Name), strconv.FormatBool(node.IsArchived)})
            }
        } else {
            table.Append([]string{node.Name, "No release identified. ", strconv.FormatBool(node.IsArchived)})
        }

    }
    table.Render()

    // Summary
    fmt.Println("Amount of repos in", q.Organization.Name, "is", q.Organization.Repositories.TotalCount)

}

UPDATE: Actually this might be a bug on the GitHub side. curl or net/http will get the same result (missing release-nodes-names) but GitHubs own API...Exploder will get the expected one. Sooo... bug there?

vllry commented 5 years ago

I'm also seeing a similar issue.

The following works in the GitHub GraphQL explorer:

{
  repository(owner: "kubernetes", name: "kubernetes") {
    issues(last: 5, states: OPEN) {
      edges {
        node {
          title
          url
        }
      }
    }
  }
}

But I get the error when making the query in Go.

var Query struct {
    Repository struct {
        Issues struct {
            Edges struct {
                Node struct {
                    Title string
                    Url string
                }
            }
        } `graphql:"issues(last: 5)"`
    } `graphql:"repository(owner: \"kubernetes\", name: \"kubernetes\")"`
}
err := client.Query(context.Background(), &Query, nil)
dmitshur commented 5 years ago

@vllry Edges should be a slice of structs rather than a single strict.

vllry commented 5 years ago

Thank you @dmitshur, sorry for the misunderstanding!

tooilxui commented 4 years ago

I meet same problem too, I have checked value in *out.Data and it is correct, but got error after jsonutil.UnmarshalGraphQL

here is my code :

    client := graphql.NewClient("http://192.1.1.115:8080/v1/graphql",http.DefaultClient)

    var query struct {
        erp_token []struct {
            token graphql.String `json:"token"`
            last_used graphql.String `json:"last_used"`
        } `json:"erp_token"`
    }

    err := client.Query(context.Background(), &query, nil)
    if err != nil {
                // I got `struct field for "erp_token" doesn't exist in any of 1 places to unmarshal` here.
        fmt.Println(err)
    }

and this is what I got in *out.Data

    "erp_token": [
      {
        "token": "103ad542-ea30-46d8-bfae-b8f3f4c60c58",
        "last_used": "2019-11-21T17:12:55"
      }
    ]

did i do anything wrong ?

dmitshur commented 4 years ago

@tooilxui I'm seeing two issues in your query variable. You're using unexported fields, and you're using json: struct field tags. You should use exported fields and graphql: struct field tags instead:

var query struct {
    ERPToken []struct {
        Token    graphql.String `graphql:"token"`
        LastUsed graphql.String `graphql:"last_used"`
    } `graphql:"erp_token"`
}

See https://github.com/shurcooL/githubv4#arguments-and-variables.