Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ func NewClient(opts ...ClientOption) *Client {
func AddHeader(name, value string) ClientOption {
return func(tr http.RoundTripper) http.RoundTripper {
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
req.Header.Add(name, value)
// prevent the token from leaking to non-GitHub hosts
// TODO: GHE support
if !strings.EqualFold(name, "Authorization") || strings.HasSuffix(req.URL.Hostname(), ".github.com") {
req.Header.Add(name, value)
}
return tr.RoundTrip(req)
}}
}
Expand All @@ -45,7 +49,11 @@ func AddHeader(name, value string) ClientOption {
func AddHeaderFunc(name string, value func() string) ClientOption {
return func(tr http.RoundTripper) http.RoundTripper {
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
req.Header.Add(name, value())
// prevent the token from leaking to non-GitHub hosts
// TODO: GHE support
if !strings.EqualFold(name, "Authorization") || strings.HasSuffix(req.URL.Hostname(), ".github.com") {
req.Header.Add(name, value())
}
return tr.RoundTrip(req)
}}
}
Expand Down
43 changes: 32 additions & 11 deletions pkg/cmd/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type ApiOptions struct {
RequestMethod string
RequestMethodPassed bool
RequestPath string
RequestInputFile string
MagicFields []string
RawFields []string
RequestHeaders []string
Expand Down Expand Up @@ -60,6 +61,10 @@ on the format of the value:
appropriate JSON types;
- if the value starts with "@", the rest of the value is interpreted as a
filename to read the value from. Pass "-" to read from standard input.

Raw request body may be passed from the outside via a file specified by '--input'.
Pass "-" to read from standard input. In this mode, parameters specified via
'--field' flags are serialized into URL query parameters.
`,
Args: cobra.ExactArgs(1),
RunE: func(c *cobra.Command, args []string) error {
Expand All @@ -78,6 +83,7 @@ on the format of the value:
cmd.Flags().StringArrayVarP(&opts.RawFields, "raw-field", "f", nil, "Add a string parameter")
cmd.Flags().StringArrayVarP(&opts.RequestHeaders, "header", "H", nil, "Add an additional HTTP request header")
cmd.Flags().BoolVarP(&opts.ShowResponseHeaders, "include", "i", false, "Include HTTP response headers in the output")
cmd.Flags().StringVar(&opts.RequestInputFile, "input", "", "The file to use as body for the HTTP request")
return cmd
}

Expand All @@ -88,16 +94,30 @@ func apiRun(opts *ApiOptions) error {
}

method := opts.RequestMethod
if len(params) > 0 && !opts.RequestMethodPassed {
requestPath := opts.RequestPath
requestHeaders := opts.RequestHeaders
var requestBody interface{} = params

if !opts.RequestMethodPassed && (len(params) > 0 || opts.RequestInputFile != "") {
method = "POST"
}

if opts.RequestInputFile != "" {
file, err := openUserFile(opts.RequestInputFile, opts.IO.In)
if err != nil {
return err
}
defer file.Close()
requestPath = addQuery(requestPath, params)
requestBody = file
}

httpClient, err := opts.HttpClient()
if err != nil {
return err
}

resp, err := httpRequest(httpClient, method, opts.RequestPath, params, opts.RequestHeaders)
resp, err := httpRequest(httpClient, method, requestPath, requestBody, requestHeaders)
if err != nil {
return err
}
Expand Down Expand Up @@ -220,20 +240,21 @@ func magicFieldValue(v string, stdin io.ReadCloser) (interface{}, error) {
}

func readUserFile(fn string, stdin io.ReadCloser) ([]byte, error) {
var r io.ReadCloser
if fn == "-" {
r = stdin
} else {
var err error
r, err = os.Open(fn)
if err != nil {
return nil, err
}
r, err := openUserFile(fn, stdin)
if err != nil {
return nil, err
}
defer r.Close()
return ioutil.ReadAll(r)
}

func openUserFile(fn string, stdin io.ReadCloser) (io.ReadCloser, error) {
if fn == "-" {
return stdin, nil
}
return os.Open(fn)
}

func parseErrorResponse(r io.Reader, statusCode int) (io.Reader, string, error) {
bodyCopy := &bytes.Buffer{}
b, err := ioutil.ReadAll(io.TeeReader(r, bodyCopy))
Expand Down
81 changes: 81 additions & 0 deletions pkg/cmd/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func Test_NewCmdApi(t *testing.T) {
RequestMethod: "GET",
RequestMethodPassed: false,
RequestPath: "graphql",
RequestInputFile: "",
RawFields: []string(nil),
MagicFields: []string(nil),
RequestHeaders: []string(nil),
Expand All @@ -45,6 +46,7 @@ func Test_NewCmdApi(t *testing.T) {
RequestMethod: "DELETE",
RequestMethodPassed: true,
RequestPath: "repos/octocat/Spoon-Knife",
RequestInputFile: "",
RawFields: []string(nil),
MagicFields: []string(nil),
RequestHeaders: []string(nil),
Expand All @@ -59,6 +61,7 @@ func Test_NewCmdApi(t *testing.T) {
RequestMethod: "GET",
RequestMethodPassed: false,
RequestPath: "graphql",
RequestInputFile: "",
RawFields: []string{"query=QUERY"},
MagicFields: []string{"[email protected]"},
RequestHeaders: []string(nil),
Expand All @@ -73,13 +76,29 @@ func Test_NewCmdApi(t *testing.T) {
RequestMethod: "GET",
RequestMethodPassed: false,
RequestPath: "user",
RequestInputFile: "",
RawFields: []string(nil),
MagicFields: []string(nil),
RequestHeaders: []string{"accept: text/plain"},
ShowResponseHeaders: true,
},
wantsErr: false,
},
{
name: "with request body from file",
cli: "user --input myfile",
wants: ApiOptions{
RequestMethod: "GET",
RequestMethodPassed: false,
RequestPath: "user",
RequestInputFile: "myfile",
RawFields: []string(nil),
MagicFields: []string(nil),
RequestHeaders: []string(nil),
ShowResponseHeaders: false,
},
wantsErr: false,
},
{
name: "no arguments",
cli: "",
Expand All @@ -92,6 +111,7 @@ func Test_NewCmdApi(t *testing.T) {
assert.Equal(t, tt.wants.RequestMethod, o.RequestMethod)
assert.Equal(t, tt.wants.RequestMethodPassed, o.RequestMethodPassed)
assert.Equal(t, tt.wants.RequestPath, o.RequestPath)
assert.Equal(t, tt.wants.RequestInputFile, o.RequestInputFile)
assert.Equal(t, tt.wants.RawFields, o.RawFields)
assert.Equal(t, tt.wants.MagicFields, o.MagicFields)
assert.Equal(t, tt.wants.RequestHeaders, o.RequestHeaders)
Expand Down Expand Up @@ -226,6 +246,44 @@ func Test_apiRun(t *testing.T) {
}
}

func Test_apiRun_inputFile(t *testing.T) {
io, stdin, _, _ := iostreams.Test()
resp := &http.Response{StatusCode: 204}

options := ApiOptions{
RequestPath: "hello",
RequestInputFile: "-",
RawFields: []string{"a=b", "c=d"},

IO: io,
HttpClient: func() (*http.Client, error) {
var tr roundTripper = func(req *http.Request) (*http.Response, error) {
resp.Request = req
return resp, nil
}
return &http.Client{Transport: tr}, nil
},
}

fmt.Fprintln(stdin, "I WORK OUT")

err := apiRun(&options)
if err != nil {
t.Errorf("got error %v", err)
}

assert.Equal(t, "POST", resp.Request.Method)
assert.Equal(t, "/hello?a=b&c=d", resp.Request.URL.RequestURI())
assert.Equal(t, "", resp.Request.Header.Get("Content-Length"))
assert.Equal(t, "", resp.Request.Header.Get("Content-Type"))

bb, err := ioutil.ReadAll(resp.Request.Body)
if err != nil {
t.Errorf("got error %v", err)
}
assert.Equal(t, "I WORK OUT\n", string(bb))
}

func Test_parseFields(t *testing.T) {
io, stdin, _, _ := iostreams.Test()
fmt.Fprint(stdin, "pasted contents")
Expand Down Expand Up @@ -332,3 +390,26 @@ func Test_magicFieldValue(t *testing.T) {
})
}
}

func Test_openUserFile(t *testing.T) {
f, err := ioutil.TempFile("", "gh-test")
if err != nil {
t.Fatal(err)
}
fmt.Fprint(f, "file contents")
f.Close()
t.Cleanup(func() { os.Remove(f.Name()) })

file, err := openUserFile(f.Name(), nil)
if err != nil {
t.Fatal(err)
}
defer file.Close()

fb, err := ioutil.ReadAll(file)
if err != nil {
t.Fatal(err)
}

assert.Equal(t, "file contents", string(fb))
}
12 changes: 9 additions & 3 deletions pkg/cmd/api/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,22 @@ import (
)

func httpRequest(client *http.Client, method string, p string, params interface{}, headers []string) (*http.Response, error) {
var requestURL string
// TODO: GHE support
url := "https://api.github.com/" + p
if strings.Contains(p, "://") {
requestURL = p
} else {
requestURL = "https://api.github.com/" + p
}

var body io.Reader
var bodyIsJSON bool
isGraphQL := p == "graphql"

switch pp := params.(type) {
case map[string]interface{}:
if strings.EqualFold(method, "GET") {
url = addQuery(url, pp)
requestURL = addQuery(requestURL, pp)
} else {
for key, value := range pp {
switch vv := value.(type) {
Expand All @@ -46,7 +52,7 @@ func httpRequest(client *http.Client, method string, p string, params interface{
return nil, fmt.Errorf("unrecognized parameters type: %v", params)
}

req, err := http.NewRequest(method, url, body)
req, err := http.NewRequest(method, requestURL, body)
if err != nil {
return nil, err
}
Expand Down