nquic_error (nquic v1.0.0)

View Source

Canonical error taxonomy for nquic.

Every error that crosses the public surface of nquic or nquic_lib is shaped by this module so callers can pattern-match exhaustively on nquic:error_reason()/0.

Two responsibilities:

wrap/1 is the single funnel for unknown error terms at the public boundary; already-canonical values pass through unchanged.

Error-handling convention contract

nquic:error_reason/0 is a closed, {error, _}-wrapped, category-classified tagged union. The supported way to interrogate a value is the trio:

  • category/1: one atom from the small closed set closed | timeout | transport | tls | application | protocol | flow_control | opts | connect | listen (never crashes on a canonical value).
  • is_retryable/1: boolean().
  • format/1: human-readable iodata().

Callers should classify and branch via these functions, not by pattern-matching the inner shape (which may gain arms within a category without a major version bump; the category set will not).

This is the same convention nhttp_error follows ({error, {category(), reason()}} plus its own category/1 / is_retryable/1 / format/1). The two taxonomies are therefore structurally parallel by convention, not coupled: nquic ships no adapter and depends on no HTTP library. A consumer that needs to project nquic failures into its own error vocabulary writes a small total adapter over category/1 at its own boundary. nquic deliberately does not know nhttp exists, so it stays reusable by any non-HTTP QUIC consumer and independently releasable.

Summary

Types

Wide internal error type carried by modules that have not yet been funnelled through a constructor. Callers at the public boundary must pass these through wrap/1 before returning to the user.

Application-domain close reason carrying peer error code and phrase.

Category tag returned by category/1, useful for telemetry.

Closed error taxonomy returned by every public function. The outer arm classifies the failure; the inner value carries detail without growing the union.

Flow control or congestion-control blocking outcomes.

Options / configuration / API-misuse errors.

Protocol-level errors: wire format, parser, RFC 9000 violations.

Phase tag for {timeout, Phase} outcomes.

TLS-layer error reasons (peer alerts or local handshake validation).

Transport-domain error reasons (peer CONNECTION_CLOSE 0x1c or local I/O).

Functions

Application-domain CONNECTION_CLOSE from the peer.

Category tag for telemetry / log fields.

Terminal connection state.

Client-side DNS / socket open / connect failure (POSIX atom).

Local flow control / congestion blocking.

Human-readable rendering for log lines.

Map a peer-sent #connection_close{} record to a canonical {transport, _} or {application, Code, Reason} error.

Map a TLS-handshake-side internal failure (no-PSK, binder mismatch, malformed finished, ...) to a canonical {tls, _} error.

Map a raw POSIX atom from a socket / inet call to the matching canonical bucket according to which operation produced it. Origin is one of connect, listen, transport.

Map a TLS alert atom or {tls_alert, _} record to a canonical {tls, _} error.

Whether the caller may sensibly retry after this error.

Server-side socket bind / listen failure (POSIX atom).

Options / configuration / API-misuse error.

Wire-format / protocol violation detected locally.

Timeout in a named phase.

TLS-layer error: peer alert or local handshake validation.

Transport-layer error: peer CONNECTION_CLOSE (0x1c) or local I/O.

Normalise any term into the canonical taxonomy. Already-canonical values pass through unchanged.

Generic dispatcher used by wrap/1. Exported for the rare site that already destructured the {error, _} tuple and only has the payload.

Wrap an {ok, _} | {error, _} result so the error arm is canonical. The ok and {ok, _} arms pass through unchanged.

Types

any_reason()

-type any_reason() :: error_reason() | atom() | tuple() | binary() | map() | list() | integer().

Wide internal error type carried by modules that have not yet been funnelled through a constructor. Callers at the public boundary must pass these through wrap/1 before returning to the user.

application_reason()

-type application_reason() :: {non_neg_integer(), binary()}.

Application-domain close reason carrying peer error code and phrase.

category()

-type category() ::
          closed | timeout | transport | tls | application | protocol | flow_control | opts | connect |
          listen.

Category tag returned by category/1, useful for telemetry.

error_reason()

-type error_reason() ::
          closed | fin |
          {timeout, timeout_phase()} |
          {transport, transport_reason()} |
          {tls, tls_reason()} |
          {application, non_neg_integer(), binary()} |
          {protocol, protocol_reason()} |
          {flow_control, flow_control_reason()} |
          {opts, opts_reason()} |
          {connect, inet:posix() | atom()} |
          {listen, inet:posix() | atom()}.

Closed error taxonomy returned by every public function. The outer arm classifies the failure; the inner value carries detail without growing the union.

flow_control_reason()

-type flow_control_reason() ::
          flow_control_error | stream_limit_error | congestion_control_blocked | eagain | partial_send |
          stream_blocked.

Flow control or congestion-control blocking outcomes.

opts_reason()

-type opts_reason() ::
          not_owner | not_found | not_connected | not_established | not_writable | unknown_request |
          unknown_stream | invalid_stream | invalid_stream_id | stream_not_found | stream_closed |
          stream_reset | empty | no_data | sups_not_ready | draining | ctx_requires_wait |
          not_supported_in_mode |
          {missing_option, atom()} |
          {misplaced_option, atom()} |
          {certfile, atom()} |
          {unsupported_cipher_suite, binary() | atom()} |
          {already_started, pid()}.

Options / configuration / API-misuse errors.

protocol_reason()

-type protocol_reason() ::
          protocol_violation | frame_encoding_error | packet_too_short | integrity_check_failed |
          final_size_error | migration_disabled | duplicate_parameter | truncated_param_value |
          transport_parameter_error | no_available_cids | retry_token_too_short | invalid_retry_token |
          datagrams_not_negotiated | datagram_too_large | invalid_packet | incomplete_binary |
          key_update_pending | no_initial_keys | no_zero_rtt_keys | no_probe_needed | overflow |
          stream_state_error | decrypt_failed |
          {transport_parameter_error, atom()} |
          {processing_failed, dynamic()} |
          {encoding_failed, dynamic()} |
          {flight_generation_failed, dynamic()} |
          {decrypt_failed, dynamic()}.

Protocol-level errors: wire format, parser, RFC 9000 violations.

timeout_phase()

-type timeout_phase() :: handshake | idle | recv | send | accept.

Phase tag for {timeout, Phase} outcomes.

tls_reason()

-type tls_reason() ::
          unexpected_message | handshake_failure | bad_certificate | unsupported_certificate |
          certificate_revoked | certificate_expired | certificate_unknown | illegal_parameter |
          unknown_ca | access_denied | decode_error | decrypt_error | protocol_version |
          insufficient_security | internal_error | inappropriate_fallback | user_canceled |
          missing_extension | unsupported_extension | unrecognized_name |
          bad_certificate_status_response | unknown_psk_identity | certificate_required |
          no_application_protocol | no_psk | no_matching_psk | no_static_key | no_peercert |
          binder_mismatch | binder_verification_failed | psk_identity_binder_mismatch |
          client_finished_verification_failed | malformed_finished | malformed_new_session_ticket |
          not_new_session_ticket | invalid_ticket_cipher | invalid_ticket_format |
          ticket_decrypt_failed | ticket_too_short |
          {bad_cert, dynamic()} |
          {hostname_mismatch, binary()} |
          {alert, atom()}.

TLS-layer error reasons (peer alerts or local handshake validation).

transport_reason()

-type transport_reason() ::
          no_error | internal_error | connection_refused | flow_control_error | stream_limit_error |
          stream_state_error | final_size_error | frame_encoding_error | transport_parameter_error |
          connection_id_limit_error | protocol_violation | invalid_token | application_error |
          crypto_buffer_exceeded | key_update_error | aead_limit_reached | no_viable_path |
          crypto_error | idle_timeout | stateless_reset | version_negotiation | closed_by_peer |
          {posix, inet:posix()} |
          {peer_close, non_neg_integer(), binary()}.

Transport-domain error reasons (peer CONNECTION_CLOSE 0x1c or local I/O).

Functions

application(Code, Reason)

-spec application(non_neg_integer(), binary()) -> {error, error_reason()}.

Application-domain CONNECTION_CLOSE from the peer.

category/1

-spec category(error_reason()) -> category().

Category tag for telemetry / log fields.

closed()

-spec closed() -> {error, error_reason()}.

Terminal connection state.

connect(Reason)

-spec connect(inet:posix() | atom()) -> {error, error_reason()}.

Client-side DNS / socket open / connect failure (POSIX atom).

flow_control(Reason)

-spec flow_control(flow_control_reason()) -> {error, error_reason()}.

Local flow control / congestion blocking.

format/1

-spec format(error_reason()) -> iolist().

Human-readable rendering for log lines.

from_connection_close/1

-spec from_connection_close(#connection_close{error_code :: non_neg_integer(),
                                              frame_type :: non_neg_integer(),
                                              reason_phrase :: binary(),
                                              is_application :: boolean()}) ->
                               {error, error_reason()}.

Map a peer-sent #connection_close{} record to a canonical {transport, _} or {application, Code, Reason} error.

from_handshake/1

-spec from_handshake(atom() | tuple()) -> {error, error_reason()}.

Map a TLS-handshake-side internal failure (no-PSK, binder mismatch, malformed finished, ...) to a canonical {tls, _} error.

from_socket/2

-spec from_socket(connect | listen | transport, inet:posix() | atom()) -> {error, error_reason()}.

Map a raw POSIX atom from a socket / inet call to the matching canonical bucket according to which operation produced it. Origin is one of connect, listen, transport.

from_tls_alert/1

-spec from_tls_alert(atom() | tuple()) -> {error, error_reason()}.

Map a TLS alert atom or {tls_alert, _} record to a canonical {tls, _} error.

is_retryable/1

-spec is_retryable(error_reason()) -> boolean().

Whether the caller may sensibly retry after this error.

listen(Reason)

-spec listen(inet:posix() | atom()) -> {error, error_reason()}.

Server-side socket bind / listen failure (POSIX atom).

opts(Reason)

-spec opts(opts_reason()) -> {error, error_reason()}.

Options / configuration / API-misuse error.

protocol(Reason)

-spec protocol(protocol_reason()) -> {error, error_reason()}.

Wire-format / protocol violation detected locally.

timeout(Phase)

-spec timeout(timeout_phase()) -> {error, error_reason()}.

Timeout in a named phase.

tls(Reason)

-spec tls(tls_reason()) -> {error, error_reason()}.

TLS-layer error: peer alert or local handshake validation.

transport(Reason)

-spec transport(transport_reason()) -> {error, error_reason()}.

Transport-layer error: peer CONNECTION_CLOSE (0x1c) or local I/O.

wrap/1

-spec wrap(term()) -> {error, error_reason()}.

Normalise any term into the canonical taxonomy. Already-canonical values pass through unchanged.

wrap_reason/1

-spec wrap_reason(term()) -> {error, error_reason()}.

Generic dispatcher used by wrap/1. Exported for the rare site that already destructured the {error, _} tuple and only has the payload.

wrap_result/1

-spec wrap_result(ok | {ok, T} | {error, term()}) -> ok | {ok, T} | {error, error_reason()}.

Wrap an {ok, _} | {error, _} result so the error arm is canonical. The ok and {ok, _} arms pass through unchanged.