Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
ba229ec
docs(#33): design for removing terrors::OneOf error unions
joeldsouzax Jul 3, 2026
c844932
docs(#33): implementation plan for removing terrors::OneOf
joeldsouzax Jul 3, 2026
daa6a08
feat(#33): add MatterBuildError enum replacing OneOf union
joeldsouzax Jul 4, 2026
cbeae1a
refactor(#33): matter builder returns MatterBuildError, drop OneOf
joeldsouzax Jul 4, 2026
866fe65
feat(#33): add VerificationError enum replacing OneOf union
joeldsouzax Jul 4, 2026
eaeaf48
refactor(#33): crypto verify returns VerificationError, drop OneOf
joeldsouzax Jul 4, 2026
b798dd8
refactor(#33): indexer builder returns bare error types, drop OneOf
joeldsouzax Jul 4, 2026
39c0027
fix(#33): serder routes ParsingError to a parsing-domain variant, not…
joeldsouzax Jul 4, 2026
f36e5f4
refactor(#33): migrate stream/parse.rs off terrors, remove unreachabl…
joeldsouzax Jul 4, 2026
9c070e6
test(#33): pin both routing arms of map_qb64_error + merge serder imp…
joeldsouzax Jul 4, 2026
3f0093d
docs(#33): record discovered Task 6b (stream/parse.rs) in plan
joeldsouzax Jul 4, 2026
45a789e
chore(#33): drop terrors dependency
joeldsouzax Jul 4, 2026
572012e
docs(#33): changelog + CLAUDE.md error-handling convention (thiserror…
joeldsouzax Jul 4, 2026
ef1b0a8
fix(#33): accept 'unparseable' spelling and fix comment typo for nix …
joeldsouzax Jul 4, 2026
5bb9f63
docs(#33): flag new SerderError variant as breaking in changelog
joeldsouzax Jul 4, 2026
c44c8db
fix(#33): bounds-check qb64 lead-byte slices (fuzz-found panic)
joeldsouzax Jul 4, 2026
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
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **error ergonomics (#33):** removed the `terrors::OneOf` error-union layer in
favour of purpose-built `thiserror` enums. **Breaking** (MINOR under 0.x):
- `MatterBuilder::{from_qualified_base64, from_qualified_base2, build}` now return
`Result<_, MatterBuildError>` (variants `Parsing`, `Validation`) instead of
`OneOf<(ParsingError, ValidationError)>`.
- `crypto::verify` now returns `Result<(), VerificationError>` (variants
`Signature`, `CodeMismatch`) instead of `OneOf<(SignatureError, CodeMismatchError)>`.
- The indexer builder's parse/validation methods (`from_qb64`, `from_qb2`,
`with_index`, `with_indices`, `with_raw`) return the bare `IndexerParseError` /
`IndexerValidationError` (previously wrapped in a single-element `OneOf`).
- Consumers matching on these results switch from `.take::` / `.narrow::` to a
normal `match` on the new enums / bare types.
- `SerderError` gains a new `UnparseablePrimitive` variant (see _Fixed_ below).
As `SerderError` is public and not `#[non_exhaustive]`, this is **breaking** for
downstream exhaustive `match` on it.
- The `terrors` dependency is dropped.

### Fixed

- **serder (#33):** a malformed-but-unparseable field value no longer collapses a
`ParsingError` into `ValidationError::UnknownMatterCode(..)` via string
formatting; a new `SerderError::UnparseablePrimitive { field, source }` variant
carries the parsing error in its own failure domain.
- **stream (#33):** removed an `unreachable!()` panic on the matter-parse error path
in `stream::parse::parse_matter`; the error mapping is now a total `match`.
- **core (#33):** `MatterBuilder::from_qualified_base64` no longer panics
(`range end index N out of range for slice of length 0`) on a malformed qb64 whose
decoded buffer is shorter than the code's declared lead size (e.g. `5BAA`). The
lead-byte slices are now bounds-checked and return
`MatterBuildError::Validation(ValidationError::StructuralIntegrityError)`. Found by
the `deep-fuzz` `matter_from_qb64` target; the crash input is committed as a fuzz
corpus regression seed.

### Added

- **crypto/devx (#69):** indexed signatures (`Siger`, the form attached to KERI
Expand Down
23 changes: 18 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,23 @@ Write clean, self-documenting code. Do not add comments explaining what the code

## Error Handling

- `thiserror` for error enums.
- `terrors::OneOf` for error type unions in return types (`Result<T, OneOf<(E1, E2, ...)>>`).
- In tests, use `.err().unwrap().take::<ErrorType>()` to extract from `OneOf` — not `.unwrap_err()` + deref.
- When `OneOf` grows large (many variants), group errors into a `thiserror` enum instead.
- `thiserror` for error enums, **including error unions**. When an operation can fail
in two or more distinct domains, model the union as a dedicated `thiserror` enum with
one `#[from]` variant per source error (e.g.
`MatterBuildError { Parsing(#[from] ParsingError), Validation(#[from] ValidationError) }`).
`#[from]` keeps `?` propagation ergonomic and preserves the source chain; prefer
`#[error(transparent)]` on a variant that simply forwards to its source's `Display`.
- A single-domain fallible operation returns its **bare** error type — never wrap a lone
error in a union type.
- In tests, match the error enum directly (`matches!(e, MatterBuildError::Parsing(_))` or a
`let ... else` bind) and assert the specific variant — do not stringify.
- Name a union enum after its operation/domain, following the one-error-enum-per-module
convention (`MatterBuildError`, `VerificationError`).

> Historical note: the crate previously used `terrors::OneOf<(E1, E2, ...)>` for error
> unions. It was removed in #33 (error-ergonomics pass) in favour of the `thiserror`-enum
> convention above, which is matchable without a runtime downcast and drops a dependency.
> Do **not** reintroduce `terrors`.

## Mandatory Rules

Expand All @@ -191,7 +204,7 @@ These rules are ported from the nexus codebase, where each one earned its place

### 3. Error Handling

Builds on the [Error Handling](#error-handling) section above (`thiserror`, `terrors::OneOf`). Additionally:
Builds on the [Error Handling](#error-handling) section above (`thiserror` enums, including unions). Additionally:

- **One variant = one failure domain.** Never jam unrelated errors into an existing variant. A malformed-length error and an invalid-base64 error are different domains — give them different variants.
- **Never discard the original error** with `|_|` in `map_err`. Wrap it via `#[source]`/`#[from]`, or at minimum preserve its message.
Expand Down
7 changes: 0 additions & 7 deletions Cargo.lock

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

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ alloc = [

# Module gates
b64 = ["dep:num-traits"]
core = ["b64", "dep:base64", "dep:num-traits", "dep:strum", "dep:terrors", "dep:zeroize"]
core = ["b64", "dep:base64", "dep:num-traits", "dep:strum", "dep:zeroize"]
crypto = [
"core",
"dep:blake2",
Expand Down Expand Up @@ -93,7 +93,6 @@ sha2 = { version = "0.10.9", default-features = false, optional = true }
sha3 = { version = "0.10.8", default-features = false, optional = true }
signature = { version = "2.2.0", default-features = false, optional = true }
strum = { version = "0.28.0", default-features = false, features = ["derive", "phf"], optional = true }
terrors = { version = "0.3.3", optional = true }
thiserror = { version = "2.0.18", default-features = false }
tokio-util = { version = "0.7.15", default-features = false, features = ["codec"], optional = true }
zeroize = { version = "1.8.2", default-features = false, features = ["derive"], optional = true }
Expand Down
4 changes: 4 additions & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ extend-exclude = [
ba = "ba"
iz = "iz"
dentifier = "dentifier"
# `UnparseablePrimitive` is the chosen SerderError variant name; "unparseable"
# is a valid English spelling the default dictionary would "correct" to
# "unparsable". Keep the deliberate spelling used across code, spec, and plan.
unparseable = "unparseable"
Loading
Loading