clijson

package
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package clijson frames a machine-readable JSON payload so it survives a transport that also carries human-readable diagnostics.

Why this exists

The Web UI runs `cloop <subcommand> --json` through pkg/executor and parses the result. Every driver hands back one merged stream: localprocess tags its output executor.StreamCombined, and a container or remote agent reads a single log pipe it cannot split either. So stdout and stderr arrive interleaved in one buffer, and "the whole buffer is the JSON document" is a contract the sub-binary cannot actually keep — anything that writes a diagnostic breaks the feature, however unrelated it is.

That is not hypothetical. It has broken the suggest panel twice: once when the command printed a coloured banner before the payload (Task 20133), and again when a schema-divergence warning from statedb landed on stderr during startup, so the UI reported

could not parse suggestions: invalid character 'w' looking for beginning of value

where the 'w' was the first letter of "warning:" (Task 20325). Silencing each writer in turn treats the symptom: the diagnostics are legitimate, and the next one to be added reopens the bug. Framing the payload fixes the class — the reader can find the document no matter what shares the stream.

The frame

Emit brackets the JSON with sentinels on their own lines:

<<<cloop-json:begin>>>
{"summary":"...","suggestions":[...]}
<<<cloop-json:end>>>

Extract recovers the payload from between them and ignores everything else. The markers are deliberately not valid JSON and not plausible prose, so a diagnostic cannot be mistaken for a frame.

Index

Constants

View Source
const (
	BeginMarker = "<<<cloop-json:begin>>>"
	EndMarker   = "<<<cloop-json:end>>>"
)

Frame markers. Each occupies its own line so a reader scanning text can see them, and neither is valid JSON so neither can be confused with a payload.

Changing these is a wire-format break between a hub and any differently versioned cloop binary it invokes — see Extract, which falls back to the unframed heuristic so the break degrades instead of failing.

Variables

View Source
var ErrNoPayload = errors.New("clijson: no JSON payload in output")

ErrNoPayload reports that buf held no JSON document: neither a frame nor anything the unframed fallback could recognise. Callers use errors.Is to tell "the command produced no result" from "the result would not parse".

Functions

func Emit

func Emit(w io.Writer, v any) error

Emit writes v to w as a framed JSON document.

The payload is marshalled fully before anything is written, then handed to w in a single Write. That matters because stdout and stderr are usually the same pipe by the time this runs: a multi-call encoder can have a concurrent diagnostic interleaved between its writes, landing inside the document. One write cannot be split that way for payloads up to PIPE_BUF, and is materially harder to split above it.

func Extract

func Extract(buf []byte) ([]byte, error)

Extract recovers the JSON document from buf, which may also contain diagnostics written to a stream merged with the payload's.

A framed payload is preferred. When no frame is present Extract falls back to the pre-framing heuristic — the span from the first '{' to the last '}' — so a hub still reads a result from an older binary that predates Emit. The fallback is the fragile behaviour this package exists to replace, and is only ever reached when the frame is genuinely absent.

On failure the error quotes the leading output, because the operator's next question is always "then what did it print instead?" and the raw decoder error ("invalid character 'w'") does not answer it.

func Unmarshal

func Unmarshal(buf []byte, v any) error

Unmarshal extracts the payload from buf and decodes it into v.

The decode error is wrapped with the leading output for the same reason Extract's is: a bare json error names a byte offset into a buffer the operator cannot see.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL