diff --git a/integration/integration_test.go b/integration/integration_test.go
index 76c4204..e8b9734 100644
--- a/integration/integration_test.go
+++ b/integration/integration_test.go
@@ -202,7 +202,7 @@ func TestMain(t *testing.T) {
t.Fatalf("unexpected body: %s", req.body)
}
- res = runFetch(t, fetchPath, server.URL, "--json", "--data", `{"key":"val"}`)
+ res = runFetch(t, fetchPath, server.URL, "--json", `{"key":"val"}`)
assertExitCode(t, 0, res)
req = <-chReq
if req.body != `{"key":"val"}` {
@@ -212,7 +212,7 @@ func TestMain(t *testing.T) {
t.Fatalf("unexpected content-type: %s", h)
}
- res = runFetch(t, fetchPath, server.URL, "--xml", "--data", ``)
+ res = runFetch(t, fetchPath, server.URL, "--xml", ``)
assertExitCode(t, 0, res)
req = <-chReq
if req.body != `` {
diff --git a/internal/cli/app.go b/internal/cli/app.go
index d0cdd18..1552dc0 100644
--- a/internal/cli/app.go
+++ b/internal/cli/app.go
@@ -28,13 +28,13 @@ type App struct {
Edit bool
Form []core.KeyVal
Help bool
- JSON bool
+ JSON io.Reader
Method string
Multipart []core.KeyVal
Output string
Update bool
Version bool
- XML bool
+ XML io.Reader
}
func (a *App) PrintHelp(p *core.Printer) {
@@ -75,8 +75,7 @@ func (a *App) CLI() *CLI {
},
ExclusiveFlags: [][]string{
{"aws-sigv4", "basic", "bearer"},
- {"data", "form", "multipart"},
- {"form", "json", "multipart", "xml"},
+ {"data", "form", "json", "multipart", "xml"},
},
Flags: []Flag{
{
@@ -199,28 +198,11 @@ func (a *App) CLI() *CLI {
return a.Data != nil
},
Fn: func(value string) error {
- switch {
- case len(value) == 0 || value[0] != '@':
- a.Data = strings.NewReader(value)
- case value == "@":
- a.Data = os.Stdin
- default:
- f, err := os.Open(value[1:])
- if err != nil {
- if os.IsNotExist(err) {
- return fmt.Errorf("file does not exist: '%s'", value[1:])
- }
- return err
- }
- info, err := f.Stat()
- if err != nil {
- return err
- }
- if info.IsDir() {
- return fmt.Errorf("file is a directory: '%s'", value[1:])
- }
- a.Data = f
+ r, err := requestBody(value)
+ if err != nil {
+ return err
}
+ a.Data = r
return nil
},
},
@@ -354,7 +336,7 @@ func (a *App) CLI() *CLI {
Short: "",
Long: "insecure",
Args: "",
- Description: "Accept invalid TLS certificates - DANGER!",
+ Description: "Accept invalid TLS certs (!)",
Default: "",
IsSet: func() bool {
return a.Cfg.Insecure != nil
@@ -368,14 +350,18 @@ func (a *App) CLI() *CLI {
{
Short: "j",
Long: "json",
- Args: "",
- Description: "Set the content-type to application/json",
+ Args: "[@]VALUE",
+ Description: "Send a JSON request body",
Default: "",
IsSet: func() bool {
- return a.JSON
+ return a.JSON != nil
},
Fn: func(value string) error {
- a.JSON = true
+ r, err := requestBody(value)
+ if err != nil {
+ return err
+ }
+ a.JSON = r
return nil
},
},
@@ -440,7 +426,7 @@ func (a *App) CLI() *CLI {
Short: "",
Long: "no-pager",
Args: "",
- Description: "Avoid using a pager for the response body",
+ Description: "Avoid using a pager for the output",
Default: "",
IsSet: func() bool {
return a.Cfg.NoPager != nil
@@ -523,7 +509,7 @@ func (a *App) CLI() *CLI {
Short: "t",
Long: "timeout",
Args: "SECONDS",
- Description: "Timeout in seconds applied to the request",
+ Description: "Timeout applied to the request",
Default: "",
IsSet: func() bool {
return a.Cfg.Timeout != nil
@@ -595,14 +581,18 @@ func (a *App) CLI() *CLI {
{
Short: "x",
Long: "xml",
- Args: "",
- Description: "Set the content-type to application/xml",
+ Args: "[@]VALUE",
+ Description: "Send an XML request body",
Default: "",
IsSet: func() bool {
- return a.XML
+ return a.XML != nil
},
Fn: func(value string) error {
- a.XML = true
+ r, err := requestBody(value)
+ if err != nil {
+ return err
+ }
+ a.XML = r
return nil
},
},
@@ -610,6 +600,31 @@ func (a *App) CLI() *CLI {
}
}
+func requestBody(value string) (io.Reader, error) {
+ switch {
+ case len(value) == 0 || value[0] != '@':
+ return strings.NewReader(value), nil
+ case value == "@-":
+ return os.Stdin, nil
+ default:
+ f, err := os.Open(value[1:])
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, fileNotExistsError(value[1:])
+ }
+ return nil, err
+ }
+ info, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+ if info.IsDir() {
+ return nil, fileIsDirError(value[1:])
+ }
+ return f, nil
+ }
+}
+
func cut(s, sep string) (string, string, bool) {
key, val, ok := strings.Cut(s, sep)
key = strings.TrimSpace(key)
@@ -617,11 +632,39 @@ func cut(s, sep string) (string, string, bool) {
return key, val, ok
}
+type fileNotExistsError string
+
+func (err fileNotExistsError) Error() string {
+ return fmt.Sprintf("file '%s' does not exist", string(err))
+}
+
+func (err fileNotExistsError) PrintTo(p *core.Printer) {
+ p.WriteString("file '")
+ p.Set(core.Dim)
+ p.WriteString(string(err))
+ p.Reset()
+ p.WriteString("' does not exist")
+}
+
type MissingEnvVarError struct {
EnvVar string
Flag string
}
+type fileIsDirError string
+
+func (err fileIsDirError) Error() string {
+ return fmt.Sprintf("file '%s' is a directory", string(err))
+}
+
+func (err fileIsDirError) PrintTo(p *core.Printer) {
+ p.WriteString("file '")
+ p.Set(core.Dim)
+ p.WriteString(string(err))
+ p.Reset()
+ p.WriteString("' is a directory")
+}
+
func missingEnvVarErr(envVar, flag string) *MissingEnvVarError {
return &MissingEnvVarError{
EnvVar: envVar,
diff --git a/internal/client/client.go b/internal/client/client.go
index dec53ae..b07e332 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -100,17 +100,17 @@ type RequestConfig struct {
AWSSigV4 *aws.Config
Basic *core.KeyVal
Bearer string
- Body io.Reader
+ Data io.Reader
Form []core.KeyVal
Headers []core.KeyVal
HTTP core.HTTPVersion
- JSON bool
+ JSON io.Reader
Method string
Multipart *multipart.Multipart
NoEncode bool
QueryParams []core.KeyVal
URL *url.URL
- XML bool
+ XML io.Reader
}
// NewRequest returns an *http.Request given the provided configuration.
@@ -125,15 +125,22 @@ func (c *Client) NewRequest(ctx context.Context, cfg RequestConfig) (*http.Reque
}
// Set any form or multipart bodies.
+ var body io.Reader
switch {
+ case cfg.Data != nil:
+ body = cfg.Data
case len(cfg.Form) > 0:
q := make(url.Values, len(cfg.Form))
for _, f := range cfg.Form {
q.Add(f.Key, f.Val)
}
- cfg.Body = strings.NewReader(q.Encode())
+ body = strings.NewReader(q.Encode())
+ case cfg.JSON != nil:
+ body = cfg.JSON
case cfg.Multipart != nil:
- cfg.Body = cfg.Multipart
+ body = cfg.Multipart
+ case cfg.XML != nil:
+ body = cfg.XML
}
// If no scheme was provided, use various heuristics to choose between
@@ -153,7 +160,7 @@ func (c *Client) NewRequest(ctx context.Context, cfg RequestConfig) (*http.Reque
}
// Create the initial HTTP request.
- req, err := http.NewRequestWithContext(ctx, cfg.Method, cfg.URL.String(), cfg.Body)
+ req, err := http.NewRequestWithContext(ctx, cfg.Method, cfg.URL.String(), body)
if err != nil {
return nil, err
}
@@ -181,9 +188,9 @@ func (c *Client) NewRequest(ctx context.Context, cfg RequestConfig) (*http.Reque
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
case cfg.Multipart != nil:
req.Header.Set("Content-Type", cfg.Multipart.ContentType())
- case cfg.JSON:
+ case cfg.JSON != nil:
req.Header.Set("Content-Type", "application/json")
- case cfg.XML:
+ case cfg.XML != nil:
req.Header.Set("Content-Type", "application/xml")
}
diff --git a/internal/fetch/fetch.go b/internal/fetch/fetch.go
index c31aca1..1fb6546 100644
--- a/internal/fetch/fetch.go
+++ b/internal/fetch/fetch.go
@@ -35,35 +35,34 @@ const (
)
type Request struct {
+ AWSSigv4 *aws.Config
+ Basic *core.KeyVal
+ Bearer string
+ Data io.Reader
DNSServer *url.URL
DryRun bool
Edit bool
+ Form []core.KeyVal
Format core.Format
+ Headers []core.KeyVal
HTTP core.HTTPVersion
IgnoreStatus bool
Insecure bool
+ JSON io.Reader
NoEncode bool
NoPager bool
+ Method string
+ Multipart *multipart.Multipart
Output string
PrinterHandle *core.Handle
+ Proxy *url.URL
+ QueryParams []core.KeyVal
Redirects *int
+ Timeout time.Duration
TLS uint16
+ URL *url.URL
Verbosity core.Verbosity
-
- Method string
- URL *url.URL
- Body io.Reader
- Form []core.KeyVal
- Multipart *multipart.Multipart
- Headers []core.KeyVal
- QueryParams []core.KeyVal
- AWSSigv4 *aws.Config
- Basic *core.KeyVal
- Bearer string
- JSON bool
- XML bool
- Proxy *url.URL
- Timeout time.Duration
+ XML io.Reader
}
func Fetch(ctx context.Context, r *Request) int {
@@ -103,7 +102,7 @@ func fetch(ctx context.Context, r *Request) (int, error) {
AWSSigV4: r.AWSSigv4,
Basic: r.Basic,
Bearer: r.Bearer,
- Body: r.Body,
+ Data: r.Data,
Form: r.Form,
Headers: r.Headers,
HTTP: r.HTTP,
@@ -126,9 +125,9 @@ func fetch(ctx context.Context, r *Request) (int, error) {
if r.Edit {
var extension string
switch {
- case r.JSON:
+ case r.JSON != nil:
extension = ".json"
- case r.XML:
+ case r.XML != nil:
extension = ".xml"
}
diff --git a/main.go b/main.go
index 9e426d0..6797222 100644
--- a/main.go
+++ b/main.go
@@ -109,35 +109,34 @@ func main() {
// Make the HTTP request using the parsed configuration.
req := fetch.Request{
+ AWSSigv4: app.AWSSigv4,
+ Basic: app.Basic,
+ Bearer: app.Bearer,
+ Data: app.Data,
DNSServer: app.Cfg.DNSServer,
DryRun: app.DryRun,
Edit: app.Edit,
+ Form: app.Form,
Format: app.Cfg.Format,
+ Headers: app.Cfg.Headers,
HTTP: app.Cfg.HTTP,
IgnoreStatus: getValue(app.Cfg.IgnoreStatus),
Insecure: getValue(app.Cfg.Insecure),
+ JSON: app.JSON,
+ Method: app.Method,
+ Multipart: multipart.NewMultipart(app.Multipart),
NoEncode: getValue(app.Cfg.NoEncode),
NoPager: getValue(app.Cfg.NoPager),
Output: app.Output,
PrinterHandle: printerHandle,
+ Proxy: app.Cfg.Proxy,
+ QueryParams: app.Cfg.QueryParams,
Redirects: app.Cfg.Redirects,
+ Timeout: getValue(app.Cfg.Timeout),
TLS: getValue(app.Cfg.TLS),
+ URL: app.URL,
Verbosity: verbosity,
-
- Method: app.Method,
- URL: app.URL,
- Body: app.Data,
- Form: app.Form,
- Multipart: multipart.NewMultipart(app.Multipart),
- Headers: app.Cfg.Headers,
- QueryParams: app.Cfg.QueryParams,
- AWSSigv4: app.AWSSigv4,
- Basic: app.Basic,
- Bearer: app.Bearer,
- JSON: app.JSON,
- XML: app.XML,
- Proxy: app.Cfg.Proxy,
- Timeout: getValue(app.Cfg.Timeout),
+ XML: app.XML,
}
status := fetch.Fetch(ctx, &req)
os.Exit(status)