Skip to content

refactor(lib): 2.0 shape & error-type ergonomics cleanups #536

@dekobon

Description

@dekobon

Decided (2026-06-05): nexits canonical everywhere (exit CLI alias 1 cycle, JSON key unchanged); remove the 3 unreachable MetricsError variants; FunctionSpan.nameOption<String> (drop error); add additive input() accessors to the parse errors. See the resolved-decisions comment.

Summary

A cluster of small public shape / error-type ergonomics fixes that are each a
SemVer break and so must batch into 2.0.

Findings

  1. ParseMetricError / ParseLangError expose no accessor.
    ParseMetricError(String) (src/metric_set.rs) and ParseLangError
    (src/macros/mod.rs) wrap a private String; callers can only Display
    the rejected input, not recover it programmatically. Contrast
    MetricsError::LanguageDisabled(LANG), which exposes its payload. Add
    fn input(&self) -> &str (additive — could ship pre-2.0) and finalize the
    representation at 2.0.

  2. MetricsError reserved variants. EmptyRoot, NonUtf8Path,
    ParseHasErrors (src/error.rs) are documented as never-produced today.
    Since the enum is #[non_exhaustive], the reserved variants buy nothing that
    #[non_exhaustive] doesn't, while forcing every exhaustive matcher to handle
    dead cases. At 2.0 either wire them up behind the strict-mode toggles they
    document, or remove them.

  3. Exit-metric has three spellings. Metric::Exit (Display = "exit"),
    Metric::NAMES/JSON key = "nexits", CodeMetrics::nexits field
    (src/metric_set.rs, src/spaces.rs). Metric::Display not round-tripping
    to the canonical NAMES spelling is a footgun for anyone using Display
    output as a config key. Pick one canonical string end-to-end at 2.0.
    (Distinct from fix(cli): reconcile metric-name spelling between diff --metric and check --threshold #514, which reconciled diff --metric vs check --threshold.)

  4. FunctionSpan sentinel. FunctionSpan uses name: String +
    error: bool (src/function.rs), setting name = String::new() +
    error = true on parse failure — the exact anti-pattern FuncSpace/Ops
    avoid with name: Option<String>. Change to name: Option<String> and drop
    error at 2.0.

Why 2.0-worthy

Items 2–4 are shape breaks; item 1's representation change is too (the accessor
itself is additive). Batching them keeps the 2.0 break surface coherent.

Acceptance

  • Parse errors expose their rejected input.
  • No documented-unreachable MetricsError variants remain (or they are wired
    up).
  • One canonical exit-metric spelling across Metric, the field, and Display.
  • FunctionSpan::name is Option<String>; error removed.

Part of the pre-2.0 review (#505).


Resolution

Implemented on fix/issue-536 (commit fe588965), batched into the 2.0 integration branch. See the resolution comment for full per-item detail.

  • Item 1 (breaking): Metric::Exit -> Metric::Nexits, Display -> "nexits"; exit is a hidden FromStr alias for one cycle (not in NAMES). Field/JSON key already nexits, so serialized output is unchanged.
  • Item 2 (breaking, partial): Removed NonUtf8Path + ParseHasErrors (never constructed). EmptyRoot kept -- it is still constructed at two defensive .ok_or(MetricsError::EmptyRoot)? guards (src/ops.rs, src/spaces.rs); per the STOP rule, a live error path is not deleted silently. Enum stays #[non_exhaustive].
  • Item 3 (breaking): FunctionSpan.name -> Option<String>, error dropped; wire::FunctionSpan DTO + From synced; web /function shape and tests updated.
  • Item 4 (additive): input(&self) -> &str on ParseMetricError and ParseLangError.

Full workspace tests + fmt/clippy/doc/xtask/self-scan gates pass; no snapshot drift. Issue left open for the integration branch / acceptance review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions