Skip to content

Apply 1.0 freeze-required API fixes (non_exhaustive, ShortIdError, OutputSubstitution, must_use) - #1703

Merged
spacebear21 merged 4 commits into
payjoin:masterfrom
spacebear21:api/1.0-freeze-fixes
Jul 8, 2026
Merged

Apply 1.0 freeze-required API fixes (non_exhaustive, ShortIdError, OutputSubstitution, must_use)#1703
spacebear21 merged 4 commits into
payjoin:masterfrom
spacebear21:api/1.0-freeze-fixes

Conversation

@spacebear21

@spacebear21 spacebear21 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

I ran a 1.0 API audit with Claude that surfaced these relatively low-hanging fruit issues that are best addressed before a 1.0 freeze.

Each commit is standalone:

  1. Mark public enums non-exhaustive — the receive/send session
    state/event/status/outcome enums, ReceiveSession/SendSession,
    ProtocolError, IntoUrlError, UrlParseError, ShortIdError, Version,
    MaybePayjoinExtras, and the persist transition/outcome enums. Adds the
    required wildcard arms in payjoin-cli (graceful "unknown" fallbacks in
    display and dispatch) and payjoin-ffi (unreachable!() in the infallible
    mirror-conversion From impls, which are version-locked to payjoin).
  2. Implement Error + Display for ShortIdError so it can be
    ?-propagated — it is the Err type of ShortId's TryFrom/FromStr but
    derived only Debug.
  3. Export OutputSubstitution under v2 — it was gated on the v1 feature,
    but v2 methods return it and v2 does not enable v1, so default (v2-only)
    builds could not name the type.
  4. Mark the persist transition types #[must_use] so dropping a transition
    without .save() is a compiler warning instead of a silently lost step.

Follows #1702 (bitcoin-ohttp / bitcoin-hpke insulation). Tracking: #976.

Disclosure: co-authored by Claude Code (Opus 4.8)

🤖 Generated with Claude Code

@coveralls

coveralls commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 28969165211

Coverage decreased (-0.07%) to 85.833%

Details

  • Coverage decreased (-0.07%) from the base build.
  • Patch coverage: 12 uncovered changes across 2 files (0 of 12 lines covered, 0.0%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
payjoin/src/directory.rs 10 0 0.0%
payjoin-cli/src/app/config.rs 2 0 0.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 15550
Covered Lines: 13347
Line Coverage: 85.83%
Coverage Strength: 350.61 hits per line

💛 - Coveralls

@spacebear21 spacebear21 added this to the payjoin-1.0 milestone Jul 2, 2026
Adding a variant to a public exhaustive enum is a breaking change, so
without #[non_exhaustive] these enums could not grow after 1.0 without
a 2.0. Mark only the enums where a wildcard arm has meaningful
semantics for integrators: the error enums (ProtocolError, IntoUrlError,
UrlParseError, ShortIdError), Version, and the receive/send
SessionStatus.

Keep the closed sets and state machines exhaustive: ReceiveSession and
SendSession, SessionEvent, SessionOutcome, MaybePayjoinExtras, and the
persist transition/outcome enums. A wildcard arm has no meaningful
semantics for those, since a caller cannot resume a session in an
unknown state. Downstream code would be forced into unreachable!() or
silent fallback arms that turn a new variant into a runtime failure on
live sessions. Adding a state is semantically breaking regardless of
exhaustiveness, and new session events already break event-log replay
on downgrade, so compatibility there is governed by persistence
versioning. The compile error on a new variant is the feature.

payjoin-cli keeps wildcard arms where it matches Version, mapping
unknown versions to a config error.
ShortIdError is the error type of ShortId's TryFrom<&[u8]> and FromStr
impls, but it derived only Debug. Without Display and std::error::Error
it could not be `?`-propagated into Box<dyn Error> or anyhow, forcing
callers to handle it specially.

Implement Display and Error with source() chaining to the wrapped
bech32 and slice-conversion errors.
The v2 API returns OutputSubstitution (Receiver<WantsOutputs>::
output_substitution and PayjoinExtras::output_substitution), but its
crate-root re-export was gated on the v1 feature. Since v2 does not
enable v1, default (v2-only) builds returned a value of a type callers
could not name or import.

Drop the v1 gate. The re-export lives inside the _core-gated core
module, so it is now available whenever v1 or v2 is enabled.
Every state-machine transition returns a transition wrapper whose
.save() both persists the session event and yields the next typestate.
Dropping the wrapper without calling .save() silently loses the event
and the state advance, with no diagnostic.

Add #[must_use] to the transition types so that dropping one is a
compiler warning.
@spacebear21
spacebear21 force-pushed the api/1.0-freeze-fixes branch from 5b467ea to 6f27ca3 Compare July 8, 2026 19:16
@spacebear21
spacebear21 marked this pull request as ready for review July 8, 2026 19:49
@spacebear21
spacebear21 requested a review from benalleng July 8, 2026 19:49

@benalleng benalleng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 6f27ca3 relatively straight forward. Thanks for the nice commit messages

@spacebear21
spacebear21 merged commit 17ed84b into payjoin:master Jul 8, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants