cliout

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package cliout centralizes the machine-facing stdout protocol shared by the compat CLIs. Every CLI emits a single-line JSON result on success and, on any failure, a single-line typed error JSON on stdout:

{"status":"error","code":"<CODE>","message":"<detalle>"}

An agent can parse stdout line-by-line and branch on the "code" field without scraping free-text stderr. The taxonomy is closed: callers pick the most specific applicable code from the constants below. The public compat/ API is not extended here; errors are classified by phase (which step failed) and, for replication, by errors.As against the existing compat.ConflictError.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeFileStrict

func DecodeFileStrict(path string, v any) error

DecodeFileStrict reads path and decodes it into v using a json.Decoder with DisallowUnknownFields, so an unknown key is an explicit error instead of being silently dropped. This closes the silent-degradation gap: a typo'd or unsupported config key is reported (ERR_CONFIG), not ignored, matching the "never silently degrade" principle the project promises.

func Die

func Die(code ErrorCode, err error)

Die writes err to stderr, emits the typed error envelope for err on stdout, and exits with the code's canonical process status. It is the non-returning counterpart to EmitErrorFrom and centralizes the "log to stderr, emit envelope to stdout, os.Exit" pattern shared by every compat CLI's error path. A nil err is rendered as the code itself (mirroring EmitErrorFrom); callers always pass a non-nil error in practice.

Die does not return, so any code after a Die call is unreachable to the compiler's flow analysis — there is no need for an explicit return statement following it.

func DispatchUsageMessage

func DispatchUsageMessage(firstArg string) string

DispatchUsageMessage returns the ERR_USAGE envelope message for the top-level compat dispatch when the leading token is not a known subcommand. A leading token that begins with "-" and is not a help-ish token (--help/-h/-help) means a flag was placed before the subcommand; the message orients the user to put flags after the subcommand (e.g. `compat cutover --dry-run <config>`). Every other unrecognized leading token (an unknown subcommand name, an empty arg, or a help-ish flag) keeps the generic "missing or unknown subcommand" message. The caller wraps this in Die(ErrUsage, errors.New(msg)).

helpIsh is a fixed set here (not a parameter) so the dispatch and this helper agree on exactly which leading tokens are treated as help requests rather than misplaced flags.

func EmitError

func EmitError(code ErrorCode, message string) int

EmitError writes a single-line typed error JSON to stdout and returns the exit code the caller should pass to os.Exit. The message is JSON-encoded, so embedded newlines in the underlying error never break the one-line contract.

func EmitErrorFrom

func EmitErrorFrom(code ErrorCode, err error) int

EmitErrorFrom writes the typed error JSON for err using code and returns the exit code. A nil err is rendered as the code itself.

func EmitJSON

func EmitJSON(v any) error

EmitJSON marshals v to compact, single-line JSON and writes it to stdout with a trailing newline. It is the success-path counterpart to EmitError.

func ExitCode

func ExitCode(code ErrorCode) int

ExitCode returns the process exit code for a typed error. Incorrect usage is exit 2; every other failure is exit 1, preserving each CLI's existing codes.

func ParseArgsStrict

func ParseArgsStrict(knownFlags, args []string, wantN int, hint, unexpectedMsg, duplicateMsg, countMsg string) (present map[string]bool, positional []string)

ParseArgsStrict is the shared front-end of every compat CLI: it partitions args into known boolean flags and positional arguments, rejects any unknown leading-dash token as ERR_USAGE (exit 2), rejects a duplicated recognized flag as ERR_USAGE (exit 2), and requires exactly wantN positional arguments. On a violation it prints hint to stderr, emits the ERR_USAGE envelope to stdout, and exits — it never returns on a violation. On success it returns the recognized flags that were seen and the positional tokens in order; the caller owns any flag-specific logic (e.g. --dry-run).

hint is the stderr usage hint printed before the envelope; unexpectedMsg is the envelope message for an unexpected flag (formatted with %q and the offending token); duplicateMsg is the envelope message for a repeated recognized flag (formatted with %q and the offending token); countMsg is the envelope message for a wrong positional count. They are caller-supplied so each CLI keeps its existing, documented usage strings and envelope messages byte-for-byte — this helper only removes the duplicated SplitArgs + fmt.Fprintln + os.Exit plumbing, never the observable text or exit codes.

func ResolveSchema

func ResolveSchema(configPath, ref string, inline compat.Schema) (compat.Schema, error)

ResolveSchema picks the canonical schema for a cutover/migration config from exactly one of the inline `schema` field or a `schema_ref` path. inline is the decoded inline schema; ref is the schema_ref value. Exactly one must be populated: inline is "populated" when it declares at least one table, and ref is "populated" when non-empty. Both or neither is an error (ERR_CONFIG). When ref is set, the referenced schema file is loaded relative to configPath. The returned error is meant for ERR_CONFIG.

Treating an empty inline schema (zero tables) as "absent" is what stops a config that omits both from running with no schema at all — the prior silent-degradation bug.

func SplitArgs

func SplitArgs(knownFlags []string, args []string) (present map[string]bool, positional []string, unexpected string, duplicate string, ok bool)

SplitArgs partitions a CLI argument list into recognized boolean flags and positional arguments. Every flag in this CLI surface is value-less (e.g. `--dry-run`), so flags never consume the following token.

A bare "--" token is the standard end-of-flags separator: once seen, every later token is positional even if it begins with "-" (so `compat audit -- --raro.json` treats `--raro.json` as the config path), and "--" itself is discarded. This lets a user pass a config path that starts with "-".

A token that begins with "-" (and is not "--") and is not one of knownFlags is an unexpected flag: ok is false and unexpected is the offending token, so the caller emits ErrUsage (exit 2). This makes ERR_USAGE actually cover "unexpected flag" as the docs claim, instead of letting an unknown flag fall through to the positional config path. A recognized flag seen more than once (e.g. `--dry-run --dry-run`) is a duplicate: ok is false and duplicate is the offending token, so the caller emits ErrUsage (exit 2) with a "duplicate flag" message — a repeated boolean flag usually signals a user mistake that should not pass unnoticed.

present holds the recognized flags that were seen (empty when knownFlags is empty, e.g. for the audit/copy subcommands). positional holds the non-flag tokens in order; the caller still validates the expected count.

Types

type ErrorCode

type ErrorCode string

ErrorCode is one value from the closed CLI error taxonomy.

const (
	// ErrUsage is incorrect CLI invocation (wrong argument count); exit 2.
	ErrUsage ErrorCode = "ERR_USAGE"
	// ErrConfig is an unreadable or invalid JSON config (file read, unmarshal,
	// or contract validation). Exit 1.
	ErrConfig ErrorCode = "ERR_CONFIG"
	// ErrAuditNotExact is a required feature whose audit status is not exact.
	// The feature findings are still emitted before this error line. Exit 1.
	ErrAuditNotExact ErrorCode = "ERR_AUDIT_NOT_EXACT"
	// ErrConnectSource is a failure to reach or open the source store. Exit 1.
	ErrConnectSource ErrorCode = "ERR_CONNECT_SOURCE"
	// ErrConnectDestination is a failure to reach or open the destination store. Exit 1.
	ErrConnectDestination ErrorCode = "ERR_CONNECT_DESTINATION"
	// ErrSchema is a schema validation or ApplySchema failure. Exit 1.
	ErrSchema ErrorCode = "ERR_SCHEMA"
	// ErrSnapshot is an export/import snapshot failure. Exit 1.
	ErrSnapshot ErrorCode = "ERR_SNAPSHOT"
	// ErrReplicationConflict is a compat.ConflictError raised while replaying
	// the change journal during catch-up. Exit 1.
	ErrReplicationConflict ErrorCode = "ERR_REPLICATION_CONFLICT"
	// ErrCapture is a change-capture install or read failure. Exit 1.
	ErrCapture ErrorCode = "ERR_CAPTURE"
	// ErrVerifyDiverged is a digest mismatch at verification. The CLI still
	// emits its diverged result JSON with this code. Exit 1.
	ErrVerifyDiverged ErrorCode = "ERR_VERIFY_DIVERGED"
	// ErrInternal is any failure not covered by a more specific code. Exit 1.
	ErrInternal ErrorCode = "ERR_INTERNAL"
)

func ReplicationCode

func ReplicationCode(err error) ErrorCode

ReplicationCode classifies an error from the catch-up drain loop. A true ConflictError raised while applying the journal is ErrReplicationConflict; anything else from the drain is ErrInternal. Capture-install and capture-read failures are classified by the caller as ErrCapture before they reach this helper.

Jump to

Keyboard shortcuts

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