Skip to content

feat!: define error codes in an enum and expose them in new variants - #138

Open
Frando wants to merge 4 commits into
mainfrom
Frando/error-cases-variants
Open

Frando wants to merge 4 commits into
mainfrom
Frando/error-cases-variants

Conversation

@Frando

@Frando Frando commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

This continues the work started in #130 and makes things fully consistent. It properly separates error codes between ones for requests and for channel messages, and exposes the stream resets properly in the corresponding RecvErrors. It adds new error variants and proper docs around them, stating clearly who causes which kind of failure.

Notes and open questions

I'm not super sure if all of them provide much value downstream, but this now fully expresses all variants that we support, and I think it is good to provide consistency in these errors and variants now. If we think it is too much, we can collapse some, but I'd still like to keep the proper error codes. IMO, having these variants doesn't hurt, and to me it feels correct to support all cases properly.

Breaking changes

All these changes only concern you if you match on specific error cases. If not all code continues to work as previously.

  • removed: the public constants rpc::ERROR_CODE_MAX_MESSAGE_SIZE_EXCEEDED, ERROR_CODE_ENCODE_FAILED, ERROR_CODE_DECODE_FAILED, and ERROR_CODE_ABORTED. Use the new rpc::ErrorCode enum with to_code and from_code.
  • changed: SendError::MaxMessageSizeExceeded and {mpsc,oneshot}::RecvError::MaxMessageSizeExceeded are renamed to MaxSizeExceeded. Each side now gets it only if it found the too large message itself. The other side gets Remote { code: ErrorCode::MessageMaxSizeExceeded }.
  • changed: WriteError::MaxMessageSizeExceeded is renamed to MaxSizeExceeded, and the client gets it only if it found the too large request itself. WriteError::Io (which held encode failures) is now EncodeFailed, and WriteError::Noq is replaced by WriteError::Io with an io::Error.
  • changed: ReadRequestError::MaxMessageSizeExceeded and InvalidRequest are renamed to MaxSizeExceeded and Malformed.
  • added: Remote { code: ErrorCode } on SendError, {mpsc,oneshot}::RecvError, and WriteError, for an irpc error code that the peer reset or stopped the stream with. Before, these cases were Io errors.
  • added: SendError::EncodeFailed and {mpsc,oneshot}::RecvError::DecodeFailed, for a message that this side could not encode or decode. Before, these cases were Io errors.
  • added: remote_code() on SendError, {mpsc,oneshot}::RecvError, WriteError, and irpc::Error. It returns the irpc error code that the peer sent, from a Remote error or from the close of the connection.

Behavior changes:

  • the error codes are renumbered, requests first: 0 Closed, 1 RequestAborted, 2 RequestMaxSizeExceeded, 3 RequestEncodeFailed, 4 RequestDecodeFailed, 5 MessageMaxSizeExceeded, 6 MessageEncodeFailed, 7 MessageDecodeFailed. Before, codes 1 to 3 meant too large, encode failed, and decode failed for both requests and channel messages, and code 4 was aborted.
  • server rejects a bad request with RequestMaxSizeExceeded or RequestDecodeFailed on both streams and for the connection close. Before, it used code 1 (too large) or code 3 (does not decode).
  • a client that cannot encode its request resets the stream with RequestMaxSizeExceeded or RequestEncodeFailed instead of RequestAborted.
  • when the server skips a request whose stream the client reset, it resets its side of the stream with RequestAborted instead of code 0.
  • a Remote error converts to an io::Error with ConnectionReset, as the reset or stop did before. The other new variants convert with InvalidData. A reset or stop with code 0 stays an Io error, except that a sender still gets ReceiverClosed.
  • after a remote send fails, later sends on the sender and its clones get the same error if the receiver ended the channel, and Io(BrokenPipe) if this sender closed it, for example after an io error, a message that does not encode or is too large, or a cancelled send.

@Frando
Frando requested a review from rklaehn October 9, 2026 14:11
@Frando
Frando force-pushed the Frando/error-cases-variants branch from f551ec2 to 31543e7 Compare October 9, 2026 22:27
@Frando
Frando changed the base branch from Frando/error-cases to main October 9, 2026 22:33
@Frando
Frando marked this pull request as draft October 9, 2026 22:33
@Frando
Frando marked this pull request as ready for review October 12, 2026 08:50
@rklaehn

rklaehn commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

🤖 In read_request_inner, the ReadFrame::Reset branch now just continues, and dropping send finishes the response stream. If an update fails to encode, its reset can overtake the request, so the server skips it, and a client with an mpsc response then gets Ok(None) (looks like a clean completion) instead of an error. Suggest send.reset(ErrorCode::RequestAborted.into()).ok(); before continue, plus an mpsc variant of reset_before_read_skips_request.

The codes were public constants, and code compared stream codes against
them one by one. `ErrorCode` defines each code once, as its
discriminant, with `to_code` and `from_code`, and converts into the
`VarInt` that noq takes. The code and its docs use it everywhere
instead of the constants, also for code 0. The behavior is the same.
@Frando
Frando force-pushed the Frando/error-cases-variants branch from 2745e94 to 5f07f08 Compare October 12, 2026 13:33
@Frando Frando changed the title refactor: define error codes in an enum and expose them in new variants feat!: define error codes in an enum and expose them in new variants Oct 12, 2026
…y them in Remote variants

irpc used the same codes for a bad request and for a bad channel message,
so a client could not tell a rejected request from a rejected response,
and every reset or stop from the peer reached the user as an `Io` error.

The error codes are now split into request codes (`RequestAborted`,
`RequestMaxSizeExceeded`, `RequestEncodeFailed`, `RequestDecodeFailed`)
and message codes (`MessageMaxSizeExceeded`, `MessageEncodeFailed`,
`MessageDecodeFailed`), and `ErrorCode` implements `Display`.
`SendError`, `oneshot::RecvError`, `mpsc::RecvError`, and `WriteError`
get a `Remote { code }` variant for a code that the peer reset or stopped
the stream with, and keep their own variants only for errors that this
side found: `MaxSizeExceeded`, `EncodeFailed`, and `DecodeFailed`.
`ReadRequestError` has `MaxSizeExceeded`, `Malformed`, and `DecodeFailed`.
`remote_code()` returns the code of a `Remote` error.

The server rejects a bad request with the request code on both streams and
when it closes the connection. A client that cannot encode its request
resets the stream with the request code. When the server skips a request
whose stream the client reset, it resets its side with `RequestAborted`,
so a client with an mpsc response gets an error instead of a clean end.
After a remote send fails, later sends get the same error if the receiver
ended the channel, and `Io(BrokenPipe)` if this sender closed it.
The client encoded a request, and the oneshot and mpsc senders encoded a
message, each with its own copy of the size check, the encoding, and the
reset. A message that passed the size check but then failed to encode
gave an `Io` error, and a request in that case reset its stream with
`RequestAborted` instead of `RequestEncodeFailed`. `encode` now measures
a value once, checks its size, and encodes it, and `encode_message` also
resets the stream of a channel message that does not encode.
When a default `Handler` rejects a request, it resets the streams of the
request and then closes the connection with the same code. The close
arrives first, so the client got an `Io` error, and `remote_code`
returned `None`. `remote_code` on the channel errors and on `WriteError`
now also returns the irpc code that the peer closed the connection with,
and the new `Error::remote_code` returns the code from any of the errors
in `Error`.

The code mapping impls of the channel errors now live in the channel
modules. `From<io::Error>` for the receive errors gives a `Remote` error
for a reset with an irpc code, so the receivers use `?`. This also
tightens the docs of the error codes and errors, and shortens the tests.
@Frando
Frando force-pushed the Frando/error-cases-variants branch from 2d1f676 to 96f5d23 Compare October 12, 2026 13:57
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.

2 participants