feat(api): add --verbose to show the request and response headers

--verbose prints the method, URL without query and headers of each request, then the status and headers of its response, on stderr like curl -v. Credentials in Authorization, Proxy-Authorization and Cookie are hidden.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-11 23:50:46 +02:00
1 parent 83dd4f7938
commit 6c124fc998
2 files changed
+53 -9

No files matched your search

+36 -9
View File
@@ -149,7 +149,7 @@ $ tea api -X PATCH repos/alice/project --input settings.json`
func (a *application) apiCommand() *cobra.Command {
var method, input, output string
var raw, typed, headerValues []string
var include, paginate, silent bool
var include, verbose, paginate, silent bool
var maxPages int
cmd := &cobra.Command{Use: "api <endpoint>", Short: "Make an authenticated Gitea API request", Long: apiLong, Example: apiExamples, Annotations: map[string]string{"tea.text": "api returns raw responses", "tea.rawjson": "true"}, Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
usage := func(err error) error { return clierr.Wrap(clierr.Usage, err) }
@@ -257,8 +257,13 @@ func (a *application) apiCommand() *cobra.Command {
response.color = response.tty && a.colorEnabled()
}
return (services.API{Client: target.client}).Visit(cmd.Context(), services.APIOptions{Endpoint: endpoint, Method: method, Body: body, Headers: headers, Paginate: paginate, MaxPages: maxPages}, func(r *http.Response) error {
if include {
if err := writeResponseHead(cmd.ErrOrStderr(), r); err != nil {
if verbose {
if err := writeRequestHead(cmd.ErrOrStderr(), r.Request); err != nil {
return err
}
}
if include || verbose {
if err := writeResponseHead(cmd.ErrOrStderr(), r, verbose); err != nil {
return err
}
}
@@ -277,6 +282,7 @@ func (a *application) apiCommand() *cobra.Command {
cmd.Flags().StringArrayVarP(&typed, "field", "F", nil, "Add a typed field: `name=value`; @FILE or @- reads a file or stdin")
cmd.Flags().StringVar(&output, "output-file", "", "Write the complete response to a new `file`")
cmd.Flags().BoolVarP(&include, "include", "i", false, "Print the response status and headers to stderr")
cmd.Flags().BoolVar(&verbose, "verbose", false, "Print the request and the response status and headers to stderr")
cmd.Flags().BoolVar(&silent, "silent", false, "Do not print the response body")
cmd.Flags().BoolVar(&paginate, "paginate", false, "Follow next-page links and print every page")
cmd.Flags().IntVar(&maxPages, "max-pages", 1000, "Stop --paginate after this many pages")
@@ -337,13 +343,34 @@ func (l *lastByte) Write(p []byte) (int, error) {
return l.w.Write(p)
}
// writeResponseHead prints the status line and the sorted headers of r, then a blank line.
func writeResponseHead(w io.Writer, r *http.Response) error {
// writeResponseHead prints the status line and the sorted headers of r, then a blank line; verbose
// marks each line with "< " like curl.
func writeResponseHead(w io.Writer, r *http.Response, verbose bool) error {
prefix := ""
if verbose {
prefix = "< "
}
return writeHead(w, prefix, r.Proto+" "+r.Status, r.Header)
}
// writeRequestHead prints the method, URL without query and headers of r marked with "> ", hiding
// credentials.
func writeRequestHead(w io.Writer, r *http.Request) error {
headers := r.Header.Clone()
for _, name := range []string{"Authorization", "Proxy-Authorization", "Cookie"} {
if headers.Get(name) != "" {
headers.Set(name, "(hidden)")
}
}
return writeHead(w, "> ", r.Method+" "+r.URL.Scheme+"://"+r.URL.Host+r.URL.EscapedPath(), headers)
}
func writeHead(w io.Writer, prefix, first string, headers http.Header) error {
var b strings.Builder
fmt.Fprintf(&b, "%s %s\n", r.Proto, r.Status)
for _, key := range slices.Sorted(maps.Keys(r.Header)) {
for _, value := range r.Header.Values(key) {
fmt.Fprintf(&b, "%s: %s\n", key, value)
b.WriteString(prefix + first + "\n")
for _, key := range slices.Sorted(maps.Keys(headers)) {
for _, value := range headers.Values(key) {
b.WriteString(prefix + terminal.SafeLine(key+": "+value) + "\n")
}
}
b.WriteString("\n")