Module: SpecGuard::RSpec::IngestReporter
- Defined in:
- lib/specguard/rspec/ingest_reporter.rb
Overview
specguard-ingest --json: the machine-readable renderer over the same
per-line facts the human report is built from.
Why a second renderer, on the one command that most needed it
An HTTP 400 is the only permanent verdict in this command's contract
(SpecGuard::RSpec::IngestCLI::CONTENT_REFUSAL_CODES) — a refused line is refused every
time it is offered — so the only way to land the run is to learn which
specs the platform objected to and fix the payload. The platform sends one
error per offending spec, each naming it by index, file and line
(Ingest::Payload#label, and Api::BaseController#render_bad_request
puts every one of them on the wire). Transport::Result keeps the whole
array, and then Transport::Result#reason renders three of them,
truncated to 300 characters, because it is built for the one stderr
line an in-run CI warning is allowed.
That cap is right where it was set and is not touched here. It is a cap on
a line, and this document is not a line: specguard-ingest already
prints a row per line, a summary and its folding observations, out of band
and nowhere near a CI log. So the refusal's own grounds do not reach a
second channel, and this is that channel — the command whose entire job is
to fix and re-send a refused run can now show you all of why it was
refused.
This is SPGD-305 done again for the gem's other executable, and it is a RENDERER: it reads the SpecGuard::RSpec::IngestCLI::LineResults the delivery already produced and the SpecGuard::RSpec::IngestCLI::ListedLines the listing already extracted, and decides nothing. The exit code, the statuses, the selection and the cap are all upstream of here and identical on both paths.
Why it is NOT json_reporter.rb
JSONReporter is the lint document over Linter::Result, and it mirrors
validate-intent --json --source key for key on purpose: the gem consumes
that document, so the two must not need two parsers. This one is about
deliveries, not annotations. It therefore does not claim that
document's "schema" => "open-test-intent.v1.json" or its
"mode" => "source" — naming a schema this document has nothing to do
with would assert a conformance it cannot have. "tool" says what wrote
it instead.
There is no ok and no exit_code in the document
Deliberately, and this is the one place the two renderers differ in kind
rather than in form. JSONReporter carries an ok because a linter's
verdict is a boolean. This command's is not: 0, 1 and 2 mean "accepted",
"the platform refused content" and "this tool could not do its job", and
collapsing that to a boolean would have to pick which of 1 and 2 counts as
false — the exact confusion IngestCLI's class comment is arranged
against. Restating the integer here would be a second copy of a fact the
process already exits with, free to drift from it. So the exit status
stays the one carrier of the verdict, unchanged by this flag, and the
document carries the facts it is computed from.
Which runs emit a document
By cause, not by exit code. JSONReporter's rule — a run that checked
nothing must not emit {"findings": []}, because that is how a gate that
checked nothing gets mistaken for one that found nothing — transfers, but
it does not transfer as "no exit-2 run emits a document": this command
reaches 2 with a real report on a file whose lines were delivered and
one of them never arrived.
So the line is drawn where the file was: a run that got as far as reading
"lines": [] next to a summary of zeroes is a
true statement about an empty file, and the warning naming why it was
empty is on stderr either way). A run that never got that far — a bad
flag, --from-line with --lines, no endpoint or API key, a file that
cannot be read — emits prose on stderr and nothing at all on stdout,
because there is nothing yet to be a document about.
The counts are handed in, not recomputed
summary's status counts are the ones IngestCLI computes once for
whichever renderer runs, for JSONReporter's reason: two renderers of one
result list that can disagree about how much of a file was delivered are
worse than prose alone, because the disagreement is unfalsifiable from
outside the process. That holds on the listing path too, where the only
count that can be positive is unparseable and the text summary states no
counterpart to disagree with — it is handed in anyway, so the discipline
is structural at both entry points rather than true of one by luck. The
folding observations are the same shape of thing — one grouping, rendered
here as data and there as a sentence.
Constant Summary collapse
- TOOL =
What wrote the document. Not a schema id: see the class comment.
"specguard-ingest"- MODE_DELIVER =
Whether lines were sent or only shown. The distinction a consumer most needs, because it decides whether the status counts below are verdicts or zeroes — see #summary's
attempted. "deliver"- MODE_LIST =
"list"- STATUS_LISTED =
A listed line that is a run. Its delivery-side counterparts are SpecGuard::RSpec::IngestCLI::STATUS_LABELS' keys, rendered by name (
undelivered, not the prose renderer'snot delivered) so a consumer branches on the tool's vocabulary rather than on its wording. "listed"- STATUS_UNPARSEABLE =
"unparseable"
Class Method Summary collapse
-
.render_delivery(source:, results:, counts:, foldings:) ⇒ String
One JSON document, without a trailing newline.
-
.render_listing(source:, lines:, counts:) ⇒ String
One JSON document, without a trailing newline.
Class Method Details
.render_delivery(source:, results:, counts:, foldings:) ⇒ String
Returns one JSON document, without a trailing newline.
116 117 118 119 120 121 122 123 124 |
# File 'lib/specguard/rspec/ingest_reporter.rb', line 116 def self.render_delivery(source:, results:, counts:, foldings:) document( mode: MODE_DELIVER, source: source, lines: results.map { |result| delivered(result) }, summary: summary(source, lines: results.length, counts: counts, attempted: attempted(counts)), foldings: foldings.map { |folding| folded(folding) } ) end |
.render_listing(source:, lines:, counts:) ⇒ String
Returns one JSON document, without a trailing newline.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/specguard/rspec/ingest_reporter.rb', line 133 def self.render_listing(source:, lines:, counts:) # Every delivery status is 0 and `attempted` is 0, which is this # document's way of saying what the text listing says in words: # nothing was delivered. `unparseable` is the one that can be positive, # because a line that is not a run is knowable without sending it — and # it is stated for the reason the text row states it, so a preview # cannot under-report what the delivery would do. document( mode: MODE_LIST, source: source, lines: lines.map { |line| listed(line) }, summary: summary(source, lines: lines.length, attempted: 0, counts: counts), foldings: [] ) end |