Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Public API audit — pre-1.0

This document is the authoritative public-API manifest for every
crate promoted to 1.0 in this workspace. It is committed at the repo
root (the canonical `docs/` folder is local-only per
`.git/info/exclude`); when a crate is published to crates.io, the
audit captured here is what its 1.0.0 promises.
Comment on lines +5 to +7

Generated: 2026-05-06.

Closes the audit pass from #42. The companion 1.0 work lives in #43
(MSRV policy) and #44 (coordinated release).

## How to reproduce

```bash
cargo install cargo-public-api --locked
cargo install cargo-semver-checks --locked

for crate in itch-protocol itch-tcp itch-soup itch-mold; do
cargo public-api -p "$crate" > "API-$crate.txt"
cargo semver-checks check-release -p "$crate"
done
```

`cargo public-api` prints the full public surface for one crate;
diff against this file across releases to catch unintended changes.
`cargo semver-checks check-release` validates that no change since
the last published version is breaking under SemVer.

Both tools are wired into the `make pre-publish` target (see
`Makefile`).

## `#[non_exhaustive]` policy

Applied to every error / event enum so future variants ship in a
minor release without breaking downstream `match` arms.

Concretely, all the following enums are `#[non_exhaustive]`:

- `itch_protocol::ProtocolError`
- `itch_protocol::primitives::TimestampError` (added 2026-05-06)
- `itch_tcp::TransportError`
- `itch_soup::SoupError`
- `itch_soup::LoginRejectReason` (added 2026-05-06)
- `itch_mold::MoldError`
- `itch_mold::MoldEvent`
- `itch_replay::ReplayError`
- `itch_book::BookError`
- `itch_source::SourceError`
- `itch_source::impls::RingBufferSeqStoreError`
- `itch_source_redis::RedisSeqStoreError`
- `itch_source_postgres::PostgresSeqStoreError`
- `itch_source_kafka::KafkaSourceError`

NOT applied to closed-set ITCH 5.0 enums — exhaustive `match` is
intentional for forward-incompatible protocol revisions:

- `itch_protocol::Message` (the 20 ITCH 5.0 message kinds)
- `itch_protocol::EventCode`
- `itch_protocol::Side`
- `itch_protocol::CrossType`
- `itch_protocol::ImbalanceDirection`
- `itch_protocol::MarketCategory`
- `itch_protocol::FinancialStatus`
- `itch_protocol::Authenticity`
- `itch_protocol::YesNo`
- `itch_protocol::LuldTier`
- `itch_protocol::TradingState`
- `itch_protocol::RegShoAction`
- `itch_protocol::MarketMakerMode`
- `itch_protocol::MarketParticipantState`
- `itch_protocol::BreachedLevel`
- `itch_protocol::IpoReleaseQualifier`
- `itch_protocol::Printable`
- `itch_protocol::PriceVariation`
- `itch_protocol::RpiInterestFlag`

A new ITCH revision lands as a new module (per ADR direction);
`Message` itself stays exhaustive so the codec catches every
new variant at compile time.

`itch_soup::SoupPacket` is also exhaustive — it is the SoupBinTCP
3.00 wire shape; new SoupBinTCP versions land as a separate
module.

`itch_replay::CaptureFormat` is exhaustive — application-level
flag covering Glimpse vs RawBodies; users select one explicitly.

## Per-crate public surface (summarised)

The full output of `cargo public-api -p <crate>` is reproducible
from a clean `cargo build`; this section names the headline shapes
that downstream code is most likely to touch.

### `itch-protocol`

- 20 message DTOs (`SystemEvent`, `StockDirectory`, …,
`RetailPriceImprovement`) + `Message` enum + `Header`.
- 18 closed-set ASCII enums in `enums` (see `#[non_exhaustive]`
policy above).
- Primitive newtypes: `Stock`, `Mpid`, `Price4`, `Price8`,
`Timestamp`, `StockLocate`, `TrackingNumber`, `OrderReference`,
`MatchNumber`, `Shares`. Every newtype is
`#[repr(transparent)]` so future versions can rely on layout.
- `Encode` / `Decode` traits + `Message::encode` / `decode` +
`ProtocolError`.

### `itch-tcp`

- `ItchCodec` (length-prefix Decoder / Encoder).
- `ItchConnection` type alias.
- `connect`, `bind`, `accept`.
- `Server` (issue #18 onwards).
- `TransportError` (`#[non_exhaustive]`).

### `itch-soup`

- `SoupConnection` + `login` / `login_with_timeout` +
`SoupCredentials`.
- `ResilientSoupClient` + `ResilientSoupConfig`.
- `SoupServer` + `Authenticator` trait + `AllowAllAuthenticator` /
`StaticAuthenticator`.
- `HeartbeatConfig`, `OutboundHeartbeat`, `InboundHeartbeat`.
- `SoupCodec` + `SoupPacket` (10 wire packet types).
- `LoginRequest`, `LoginAccepted`, `LoginRejectReason`
(`#[non_exhaustive]`).
- `SoupError` (`#[non_exhaustive]`).
- `DEFAULT_LOGIN_TIMEOUT`, `DEFAULT_SEND_TIMEOUT`,
`DEFAULT_BACKOFF_MIN`, `DEFAULT_BACKOFF_MAX`,
`DEFAULT_RESILIENT_LOGIN_TIMEOUT`, `DEFAULT_BROADCAST_CAPACITY`,
`DEFAULT_SHUTDOWN_GRACE`, `DEFAULT_UNSEQUENCED_INBOX_CAPACITY`.

### `itch-mold`

- `MoldStream` + `MoldConfig` + `MoldEvent` (`#[non_exhaustive]`).
- `MoldPublisher`, `MoldRequestServer`.
- `MoldPacket` + `MoldPacketHeader` + `MessageBlock` (downstream
packet codec).
- Constants: `HEADER_LEN`, `MSG_COUNT_HEARTBEAT`,
`MSG_COUNT_END_OF_SESSION`, `MAX_BLOCK_LEN`,
`DEFAULT_PACKING_MTU`, `BLOCK_LEN_PREFIX`.
- `MoldError` (`#[non_exhaustive]`).
- `PendingOverflowPolicy` (issue #22).

## Doc coverage

`cargo doc --workspace --no-deps` is required to be zero-warning
under `RUSTDOCFLAGS=-D warnings` (CI gate). Every `pub` item
carries a `///` doc comment; every fallible `pub fn` carries an
`# Errors` section. The doc gate is enforced on every PR.

## SemVer commitment (effective at 1.0.0)

From the moment a crate ships its 1.0.0 release:

- Adding a new `pub fn` / `pub const` / `pub struct` / `pub enum`
variant to a `#[non_exhaustive]` enum is **non-breaking**.
- Renaming, removing, or changing the signature of any item in
this manifest is **breaking** (major bump).
- Raising MSRV is **breaking-by-policy** (see `MSRV.md`) — a
one-minor-version deprecation window applies.
- Changing wire format (`Message::encode` / `decode` byte shape)
Comment on lines +159 to +163
is **breaking**.

These rules are mechanically validated on every release PR by
`cargo semver-checks check-release`.

## References

- ADR-0007 (independent crates).
- `MSRV.md` (MSRV policy and current floors).
- `CHANGELOG.md` per crate (per-release deltas).
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 31 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,35 @@ fuzz-list: ## List the configured fuzz targets

pre-push: fix fmt lint-fix test doc ## Canonical pre-push gate

# ---------- 1.0 release gates ----------

# Promoted crates that ship a 1.0 release. Update this list when
# adding or removing crates from the 1.0 promotion set (see #42).
RELEASE_CRATES ?= itch-protocol itch-tcp itch-soup itch-mold

public-api: ## Print the full public API of every promoted crate (requires `cargo install cargo-public-api`)
@for c in $(RELEASE_CRATES); do \
echo "==> $$c"; \
$(CARGO) public-api -p $$c || exit $$?; \
done

semver-check: ## Validate no breaking change since the last published version of each promoted crate (requires `cargo install cargo-semver-checks`)
@for c in $(RELEASE_CRATES); do \
echo "==> $$c"; \
$(CARGO) semver-checks check-release -p $$c || exit $$?; \
done

check-msrv: ## Build + test every promoted crate against the pinned MSRV (requires the MSRV toolchain installed via `rustup toolchain install`)
@MSRV=$$(grep -E '^rust-version' crates/itch-protocol/Cargo.toml | head -1 | sed -E 's/.*"([0-9.]+)".*/\1/'); \
if [ -z "$$MSRV" ]; then echo "could not detect MSRV from crates/itch-protocol/Cargo.toml"; exit 1; fi; \
Comment on lines +123 to +124
echo "MSRV: $$MSRV"; \
for c in $(RELEASE_CRATES); do \
echo "==> $$c (build)"; $(CARGO) +$$MSRV build -p $$c || exit $$?; \
echo "==> $$c (test)"; $(CARGO) +$$MSRV test -p $$c || exit $$?; \
done

pre-publish: pre-push public-api semver-check check-msrv ## Full pre-publish gate (1.0 release-readiness)

# ---------- workflow helpers ----------

workflow-list: ## List GitHub Actions workflow runs
Expand All @@ -116,4 +145,5 @@ clean: ## Cargo clean

.PHONY: help build release test run fmt fmt-check lint lint-fix check fix \
doc coverage coverage-html bench bench-save bench-compare fuzz \
pre-push workflow-list workflow-view clean
pre-push public-api semver-check check-msrv pre-publish \
workflow-list workflow-view clean
4 changes: 4 additions & 0 deletions crates/itch-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,10 @@ async fn run_soup(server: &str, username: &str, password: &str) -> ExitCode {
let detail = match reason {
LoginRejectReason::NotAuthorized => "not authorized",
LoginRejectReason::SessionUnavailable => "session unavailable",
// `LoginRejectReason` is `#[non_exhaustive]` —
// forward-compat arm for future SoupBinTCP
// reject codes.
_ => "unknown reject code",
};
error!(?reason, detail, "login rejected — terminal");
return ExitCode::from(6);
Expand Down
9 changes: 9 additions & 0 deletions crates/itch-protocol/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ this project adheres to per-crate [SemVer](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Changed

- **API audit for 1.0** (#42). Added `#[non_exhaustive]` to
`primitives::TimestampError` so future stricter validation can
add reject reasons in a minor release without breaking
downstream `match` arms. Closed-set ITCH 5.0 enums (`Message`,
`EventCode`, `Side`, `CrossType`, …) deliberately stay
exhaustive — see the workspace `API.md` for the full policy.

### Added

- **Property-based tests** (`tests/property.rs` + shared
Expand Down
5 changes: 5 additions & 0 deletions crates/itch-protocol/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ impl Shares {
pub struct Timestamp(u64);

/// Reasons a `Timestamp::try_new` can reject the input.
///
/// `#[non_exhaustive]` is intentional: future ITCH revisions or
/// stricter validation may add reject reasons; consumers must
/// match with a fallback arm.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimestampError {
/// Value exceeds the u48 range `[0, 2^48 - 1]`.
Expand Down
9 changes: 9 additions & 0 deletions crates/itch-soup/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ this project adheres to per-crate [SemVer](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Changed

- **API audit for 1.0** (#42). Added `#[non_exhaustive]` to
`LoginRejectReason` so NASDAQ's future SoupBinTCP reject codes
can ship in a minor release without breaking downstream `match`
arms. Updated `itch-client::run_soup` to add a fallback arm
("unknown reject code"). See the workspace `API.md` for the
full enum-by-enum policy.

### Added

- **Criterion bench `soup_codec`** (issue #31): per-packet
Expand Down
5 changes: 5 additions & 0 deletions crates/itch-soup/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ pub enum SoupError {
}

/// Codes carried by a Login Rejected (`J`) packet.
///
/// `#[non_exhaustive]` is intentional: NASDAQ may extend the
/// SoupBinTCP reject codes in a future revision; consumers must
/// match with a fallback arm to remain forward-compatible.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoginRejectReason {
/// `A` — Not Authorized: the username / password combination
Expand Down
Loading