feat(api): add --json, --json-fields, --jq to fj api
`fj api` was the only command that returned raw API JSON without exposing
the same projection/filtering knobs that `fj repo list`, `fj pr list`,
etc. already provide. Callers had to pipe to `python -m json.tool` or
`jq` to extract fields, which is inconsistent and discoverable only
after hitting the gap.
Wire the existing addJSONFlags / wantJSON / outputJSON helpers from
cmd/json.go so the API command participates in the same JSON output
pipeline. No behavioral change when none of the new flags are set —
default still pretty-prints JSON and writes raw bytes for non-JSON
responses.
Verified against live forgejo:
$ fj api repos/public/claude-code-proxy --jq .full_name
public/claude-code-proxy
$ fj api repos/public/claude-code-proxy --json=full_name,description
{ "description": "...", "full_name": "public/claude-code-proxy" }
$ fj api 'repos/public/claude-code-proxy/commits?limit=3' \
--jq '.[] | "\(.sha[0:8]) \(.commit.message | split("\n")[0])"'
8e550b97 Local fork: hardening + ops improvements ...
b9da198e Harden proxy auth, storage, and conversation access
6cda3631 Harden streaming, pagination, and config loading
Note: `--json=fields` requires the equals sign because the flag has
NoOptDefVal=" " (so `--json` alone is valid for "everything as JSON").
The Example block in --help documents both the `--json=` form and the
`--json-fields` alias which doesn't have that quirk.
This commit is contained in:
parent
0fda0b8679
commit
f75b831a53
1 changed files with 31 additions and 7 deletions
38
cmd/api.go
38
cmd/api.go
|
|
@ -35,7 +35,17 @@ If --field is used and no --method is specified, the method defaults to POST.`,
|
|||
fj api /users/johndoe
|
||||
|
||||
# Use raw body from stdin
|
||||
echo '{"title":"test"}' | fj api /repos/{owner}/{repo}/issues --input -`,
|
||||
echo '{"title":"test"}' | fj api /repos/{owner}/{repo}/issues --input -
|
||||
|
||||
# Filter the response with a jq expression
|
||||
fj api /repos/{owner}/{repo}/issues --jq '.[].title'
|
||||
|
||||
# Project the response down to specific fields (requires "=" because --json
|
||||
# also accepts an empty value to mean "all fields as JSON")
|
||||
fj api /repos/{owner}/{repo} --json=full_name,description,private
|
||||
|
||||
# Same projection without the "=" quirk
|
||||
fj api /repos/{owner}/{repo} --json-fields full_name,description,private`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAPI,
|
||||
}
|
||||
|
|
@ -50,6 +60,7 @@ func init() {
|
|||
apiCmd.Flags().StringArrayP("header", "H", nil, "Add an HTTP request header (key:value)")
|
||||
apiCmd.Flags().Bool("silent", false, "Do not print the response body")
|
||||
apiCmd.Flags().BoolP("include", "i", false, "Include HTTP response headers in the output")
|
||||
addJSONFlags(apiCmd, "Output the response as JSON; pass a comma-separated field list to project specific keys")
|
||||
}
|
||||
|
||||
func runAPI(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -212,18 +223,31 @@ func runAPI(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Pretty-print JSON, or output raw if not JSON
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if strings.Contains(contentType, "json") || json.Valid(respBody) {
|
||||
isJSON := strings.Contains(contentType, "json") || json.Valid(respBody)
|
||||
|
||||
// If the user asked for JSON projection or jq filtering, route through
|
||||
// the shared JSON output helpers so the API command is consistent with
|
||||
// `fj repo list`, `fj pr list`, etc.
|
||||
if wantJSON(cmd) {
|
||||
if !isJSON {
|
||||
return fmt.Errorf("--json/--json-fields/--jq requires a JSON response, but the server returned %s", contentType)
|
||||
}
|
||||
var parsed any
|
||||
if err := json.Unmarshal(respBody, &parsed); err != nil {
|
||||
return fmt.Errorf("response is not valid JSON: %w", err)
|
||||
}
|
||||
return outputJSON(cmd, parsed)
|
||||
}
|
||||
|
||||
// Pretty-print JSON by default, otherwise emit raw bytes.
|
||||
if isJSON {
|
||||
var parsed any
|
||||
if err := json.Unmarshal(respBody, &parsed); err == nil {
|
||||
enc := json.NewEncoder(ios.Out)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(parsed)
|
||||
return writeJSON(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
// Raw output for non-JSON responses
|
||||
_, err = ios.Out.Write(respBody)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue