fj api cannot read a request body from stdin or a file (no --input -, no @file) #126

Open
opened 2026-06-11 00:51:51 +00:00 by stephen · 1 comment
Owner

What

fj api can only take a request body as an inline --input '<json string>'.
There is no way to stream the body from stdin (--input -) or read it from a
file (--input @path, or -f key=@file), unlike gh api. For CI that
generates request payloads on the fly (heredocs, jq output, files produced by a
build step), this means shelling the whole JSON document onto one command line.

Evidence

--input is parsed as a literal JSON string and nothing else
(src/cli/api.rs:137):

fn build_query_or_body(args: &ApiArgs, method: &Method) -> Result<QueryAndBody> {
    if let Some(input) = &args.input {
        let body: Value = serde_json::from_str(input).context("--input must be valid JSON")?;
        return Ok((Vec::new(), Some(body)));
    }
    ...

There is no - / @file handling here or in split_kv (src/cli/api.rs:259),
and the -f / -F field parsers (src/cli/api.rs:143-167) treat every value as
a literal string / literal JSON, so -f body=@payload.json sends the literal
text @payload.json. The doc comment on the field even says values are taken
verbatim (src/cli/api.rs:40 "Send a literal JSON request body").

Why it matters for CI/automation buyers

gh api users routinely do:

jq -n --arg sha "$SHA" '{state:"success",context:"deploy",target_url:$URL}' \
  | fj api -X POST "/repos/$R/statuses/$SHA" --input -

With fj that pipe is impossible: you must inline-escape the JSON into --input,
which breaks the moment the payload contains shell metacharacters, is generated
by another tool, or exceeds a comfortable command-line length. This is the
single most common gh api write idiom and it has no fj equivalent.

Proposed shape

  • --input - reads the request body from stdin until EOF.
  • --input @path reads the request body from a file.
  • Optionally extend -F key=@path (gh-style) so a single field can be sourced
    from a file. (gh reserves @- for stdin and @file for a file on -F.)

Keep the existing inline-string behavior for any value that is not - / @....

Scope

fj api passthrough only. Sibling to the resource-command body-file gap in
rasterstate/fj#124 (-F/--body-file for issue/pr/release/milestone), but that
issue is about human prose bodies; this is about raw HTTP request bodies on the
api escape hatch.

## What `fj api` can only take a request body as an inline `--input '<json string>'`. There is no way to stream the body from **stdin** (`--input -`) or read it from a **file** (`--input @path`, or `-f key=@file`), unlike `gh api`. For CI that generates request payloads on the fly (heredocs, `jq` output, files produced by a build step), this means shelling the whole JSON document onto one command line. ## Evidence `--input` is parsed as a literal JSON string and nothing else (`src/cli/api.rs:137`): ```rust fn build_query_or_body(args: &ApiArgs, method: &Method) -> Result<QueryAndBody> { if let Some(input) = &args.input { let body: Value = serde_json::from_str(input).context("--input must be valid JSON")?; return Ok((Vec::new(), Some(body))); } ... ``` There is no `-` / `@file` handling here or in `split_kv` (`src/cli/api.rs:259`), and the `-f` / `-F` field parsers (`src/cli/api.rs:143-167`) treat every value as a literal string / literal JSON, so `-f body=@payload.json` sends the literal text `@payload.json`. The doc comment on the field even says values are taken verbatim (`src/cli/api.rs:40` "Send a literal JSON request body"). ## Why it matters for CI/automation buyers `gh api` users routinely do: ```sh jq -n --arg sha "$SHA" '{state:"success",context:"deploy",target_url:$URL}' \ | fj api -X POST "/repos/$R/statuses/$SHA" --input - ``` With fj that pipe is impossible: you must inline-escape the JSON into `--input`, which breaks the moment the payload contains shell metacharacters, is generated by another tool, or exceeds a comfortable command-line length. This is the single most common `gh api` write idiom and it has no fj equivalent. ## Proposed shape - `--input -` reads the request body from stdin until EOF. - `--input @path` reads the request body from a file. - Optionally extend `-F key=@path` (gh-style) so a single field can be sourced from a file. (gh reserves `@-` for stdin and `@file` for a file on `-F`.) Keep the existing inline-string behavior for any value that is not `-` / `@...`. ## Scope `fj api` passthrough only. Sibling to the resource-command body-file gap in rasterstate/fj#124 (`-F/--body-file` for issue/pr/release/milestone), but that issue is about human prose bodies; this is about raw HTTP request bodies on the `api` escape hatch.
Author
Owner

Converted to backlog item rasterstate/fj#136 (p1, size S).

Converted to backlog item rasterstate/fj#136 (p1, size S).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rasterstate/fj#126
No description provided.