提交 b6d9837a 编写于 作者: M Mislav Marohnić

[api] Implement GraphQL pagination

The GraphQL query has to accept the optional `endCursor` string variable
and output `pageInfo`:

    pageInfo {
      hasNextPage
      endCursor
    }
上级 424acfa8
package commands package commands
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
...@@ -189,7 +190,8 @@ func apiCommand(cmd *Command, args *Args) { ...@@ -189,7 +190,8 @@ func apiCommand(cmd *Command, args *Args) {
host = defHost.Host host = defHost.Host
} }
if path == "graphql" && params["query"] != nil { isGraphQL := path == "graphql"
if isGraphQL && params["query"] != nil {
query := params["query"].(string) query := params["query"].(string)
query = strings.Replace(query, quote("{owner}"), quote(owner), 1) query = strings.Replace(query, quote("{owner}"), quote(owner), 1)
query = strings.Replace(query, quote("{repo}"), quote(repo), 1) query = strings.Replace(query, quote("{repo}"), quote(repo), 1)
...@@ -253,8 +255,15 @@ func apiCommand(cmd *Command, args *Args) { ...@@ -253,8 +255,15 @@ func apiCommand(cmd *Command, args *Args) {
fmt.Fprintf(out, "\r\n") fmt.Fprintf(out, "\r\n")
} }
endCursor := ""
hasNextPage := false
if parseJSON && jsonType { if parseJSON && jsonType {
utils.JSONPath(out, response.Body, colorize) hasNextPage, endCursor = utils.JSONPath(out, response.Body, colorize)
} else if paginate && isGraphQL {
bodyCopy := &bytes.Buffer{}
io.Copy(out, io.TeeReader(response.Body, bodyCopy))
hasNextPage, endCursor = utils.JSONPath(ioutil.Discard, bodyCopy, false)
} else { } else {
io.Copy(out, response.Body) io.Copy(out, response.Body)
} }
...@@ -266,7 +275,16 @@ func apiCommand(cmd *Command, args *Args) { ...@@ -266,7 +275,16 @@ func apiCommand(cmd *Command, args *Args) {
requestLoop = false requestLoop = false
if paginate { if paginate {
if nextLink := response.Link("next"); nextLink != "" { if isGraphQL && hasNextPage && endCursor != "" {
if v, ok := params["variables"]; ok {
variables := v.(map[string]interface{})
variables["endCursor"] = endCursor
} else {
variables := map[string]interface{}{"endCursor": endCursor}
params["variables"] = variables
}
requestLoop = true
} else if nextLink := response.Link("next"); nextLink != "" {
path = nextLink path = nextLink
requestLoop = true requestLoop = true
} }
......
...@@ -127,6 +127,28 @@ Feature: hub api ...@@ -127,6 +127,28 @@ Feature: hub api
[{"page":3}] [{"page":3}]
""" """
Scenario: Paginate GraphQL
Given the GitHub API server:
"""
post('/graphql') {
variables = params[:variables] || {}
page = (variables["endCursor"] || 1).to_i
json :data => {
:pageInfo => {
:hasNextPage => page < 3,
:endCursor => (page+1).to_s
}
}
}
"""
When I successfully run `hub api --paginate graphql -f query=QUERY`
Then the output should contain exactly:
"""
{"data":{"pageInfo":{"hasNextPage":true,"endCursor":"2"}}}
{"data":{"pageInfo":{"hasNextPage":true,"endCursor":"3"}}}
{"data":{"pageInfo":{"hasNextPage":false,"endCursor":"4"}}}
"""
Scenario: Avoid leaking token to a 3rd party Scenario: Avoid leaking token to a 3rd party
Given the GitHub API server: Given the GitHub API server:
""" """
......
...@@ -29,10 +29,7 @@ func stateKey(s *state) string { ...@@ -29,10 +29,7 @@ func stateKey(s *state) string {
} }
} }
func printValue(token json.Token) { func JSONPath(out io.Writer, src io.Reader, colorize bool) (hasNextPage bool, endCursor string) {
}
func JSONPath(out io.Writer, src io.Reader, colorize bool) {
dec := json.NewDecoder(src) dec := json.NewDecoder(src)
dec.UseNumber() dec.UseNumber()
...@@ -84,12 +81,18 @@ func JSONPath(out io.Writer, src io.Reader, colorize bool) { ...@@ -84,12 +81,18 @@ func JSONPath(out io.Writer, src io.Reader, colorize bool) {
switch tt := token.(type) { switch tt := token.(type) {
case string: case string:
fmt.Fprintf(out, "%s\n", strings.Replace(tt, "\n", "\\n", -1)) fmt.Fprintf(out, "%s\n", strings.Replace(tt, "\n", "\\n", -1))
if strings.HasSuffix(k, ".pageInfo.endCursor") {
endCursor = tt
}
case json.Number: case json.Number:
fmt.Fprintf(out, "%s\n", color("0;35", tt)) fmt.Fprintf(out, "%s\n", color("0;35", tt))
case nil: case nil:
fmt.Fprintf(out, "\n") fmt.Fprintf(out, "\n")
case bool: case bool:
fmt.Fprintf(out, "%s\n", color("1;33", fmt.Sprintf("%v", tt))) fmt.Fprintf(out, "%s\n", color("1;33", fmt.Sprintf("%v", tt)))
if strings.HasSuffix(k, ".pageInfo.hasNextPage") {
hasNextPage = tt
}
default: default:
panic("unknown type") panic("unknown type")
} }
...@@ -97,4 +100,5 @@ func JSONPath(out io.Writer, src io.Reader, colorize bool) { ...@@ -97,4 +100,5 @@ func JSONPath(out io.Writer, src io.Reader, colorize bool) {
} }
} }
} }
return
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册