%%% @doc RFC 9298 error to HTTP status code mapping.
%%%
%%% All MASQUE failures reported during the CONNECT-UDP handshake must
%%% be returned to the client as an HTTP status response. This module
%%% centralises the mapping so server code and tests agree on the exact
%%% status code for each failure mode.
-module(masque_errors).
-export([handshake_status/1, status_reason/1]).
-include("masque.hrl").
-type handshake_error() ::
bad_method
| bad_protocol
| bad_path
| bad_port
| bad_host
| resolution_failed
| upstream_timeout
| forbidden
| loop_detected
| overload
| {other, 400..599}.
-export_type([handshake_error/0]).
%% @doc Map a handshake error to the status code MASQUE should return.
-spec handshake_status(handshake_error()) -> 400..599.
handshake_status(bad_method) -> ?MASQUE_STATUS_METHOD_NOT_ALLOWED;
handshake_status(bad_protocol) -> ?MASQUE_STATUS_NOT_IMPLEMENTED;
handshake_status(bad_path) -> ?MASQUE_STATUS_NOT_FOUND;
handshake_status(bad_port) -> ?MASQUE_STATUS_BAD_REQUEST;
handshake_status(bad_host) -> ?MASQUE_STATUS_BAD_REQUEST;
handshake_status(resolution_failed) -> ?MASQUE_STATUS_BAD_GATEWAY;
handshake_status(upstream_timeout) -> ?MASQUE_STATUS_GATEWAY_TIMEOUT;
handshake_status(forbidden) -> 403;
handshake_status(loop_detected) -> ?MASQUE_STATUS_LOOP_DETECTED;
handshake_status(overload) -> 503;
handshake_status({other, Status}) when Status >= 400, Status =< 599 -> Status.
%% @doc Short human-readable reason-phrase for the given error.
-spec status_reason(handshake_error()) -> binary().
status_reason(bad_method) -> <<"method must be CONNECT">>;
status_reason(bad_protocol) -> <<":protocol must be connect-udp">>;
status_reason(bad_path) -> <<"path does not match template">>;
status_reason(bad_port) -> <<"target_port out of range">>;
status_reason(bad_host) -> <<"target_host empty or malformed">>;
status_reason(resolution_failed) -> <<"could not resolve target">>;
status_reason(upstream_timeout) -> <<"target did not respond in time">>;
status_reason(forbidden) -> <<"target denied by policy">>;
status_reason(loop_detected) -> <<"proxy loop detected">>;
status_reason(overload) -> <<"proxy overloaded">>;
status_reason({other, _}) -> <<"request rejected">>.