(** Centralized error handling for the Requests library *) let src = Logs.Src.create "requests.error" ~doc:"HTTP Request Errors" module Log = (val Logs.src_log src : Logs.LOG) (** {1 Exception Types} *) exception Timeout exception TooManyRedirects of { url: string; count: int; max: int } exception ConnectionError of string exception HTTPError of { url: string; status: int; reason: string; body: string option; headers: Headers.t } exception AuthenticationError of string exception SSLError of string exception ProxyError of string exception EncodingError of string exception InvalidURL of string exception InvalidRequest of string (** {1 Error Type} *) type t = [ | `Timeout | `TooManyRedirects of string * int * int (* url, count, max *) | `ConnectionError of string | `HTTPError of string * int * string * string option * Headers.t (* url, status, reason, body, headers *) | `AuthenticationError of string | `SSLError of string | `ProxyError of string | `EncodingError of string | `InvalidURL of string | `InvalidRequest of string | `UnknownError of string ] (** {1 Conversion Functions} *) let of_exn = function | Timeout -> Some `Timeout | TooManyRedirects { url; count; max } -> Some (`TooManyRedirects (url, count, max)) | ConnectionError msg -> Some (`ConnectionError msg) | HTTPError { url; status; reason; body; headers } -> Some (`HTTPError (url, status, reason, body, headers)) | AuthenticationError msg -> Some (`AuthenticationError msg) | SSLError msg -> Some (`SSLError msg) | ProxyError msg -> Some (`ProxyError msg) | EncodingError msg -> Some (`EncodingError msg) | InvalidURL msg -> Some (`InvalidURL msg) | InvalidRequest msg -> Some (`InvalidRequest msg) | _ -> None let to_exn = function | `Timeout -> Timeout | `TooManyRedirects (url, count, max) -> TooManyRedirects { url; count; max } | `ConnectionError msg -> ConnectionError msg | `HTTPError (url, status, reason, body, headers) -> HTTPError { url; status; reason; body; headers } | `AuthenticationError msg -> AuthenticationError msg | `SSLError msg -> SSLError msg | `ProxyError msg -> ProxyError msg | `EncodingError msg -> EncodingError msg | `InvalidURL msg -> InvalidURL msg | `InvalidRequest msg -> InvalidRequest msg | `UnknownError msg -> Failure msg let raise error = Stdlib.raise (to_exn error) (** {1 Combinators} *) let catch f = try Ok (f ()) with | exn -> match of_exn exn with | Some err -> Error err | None -> Error (`UnknownError (Printexc.to_string exn)) let catch_async f = catch f (* In Eio, regular catch works for async too *) let map f = function | Ok x -> Ok (f x) | Error e -> Error e let bind f = function | Ok x -> f x | Error e -> Error e let both a b = match a, b with | Ok x, Ok y -> Ok (x, y) | Error e, _ -> Error e | _, Error e -> Error e let get_exn = function | Ok x -> x | Error e -> raise e let get_or ~default = function | Ok x -> x | Error _ -> default let is_retryable = function | `Timeout -> true | `ConnectionError _ -> true | `HTTPError (_, status, _, _, _) -> Status.is_retryable (Status.of_int status) | `SSLError _ -> true | `ProxyError _ -> true | _ -> false let is_client_error = function | `HTTPError (_, status, _, _, _) -> Status.is_client_error (Status.of_int status) | `AuthenticationError _ | `InvalidURL _ | `InvalidRequest _ -> true | _ -> false let is_server_error = function | `HTTPError (_, status, _, _, _) -> Status.is_server_error (Status.of_int status) | _ -> false (** {1 Pretty Printing} *) let pp ppf = function | `Timeout -> Format.fprintf ppf "@[<2>Request Timeout:@ The request timed out@]" | `TooManyRedirects (url, count, max) -> Format.fprintf ppf "@[<2>Too Many Redirects:@ Exceeded maximum redirects (%d/%d) for URL: %s@]" count max url | `ConnectionError msg -> Format.fprintf ppf "@[<2>Connection Error:@ %s@]" msg | `HTTPError (url, status, reason, body, _headers) -> Format.fprintf ppf "@[@[<2>HTTP Error %d (%s):@ URL: %s@]" status reason url; Option.iter (fun b -> Format.fprintf ppf "@,@[<2>Response Body:@ %s@]" b ) body; Format.fprintf ppf "@]" | `AuthenticationError msg -> Format.fprintf ppf "@[<2>Authentication Error:@ %s@]" msg | `SSLError msg -> Format.fprintf ppf "@[<2>SSL/TLS Error:@ %s@]" msg | `ProxyError msg -> Format.fprintf ppf "@[<2>Proxy Error:@ %s@]" msg | `EncodingError msg -> Format.fprintf ppf "@[<2>Encoding Error:@ %s@]" msg | `InvalidURL msg -> Format.fprintf ppf "@[<2>Invalid URL:@ %s@]" msg | `InvalidRequest msg -> Format.fprintf ppf "@[<2>Invalid Request:@ %s@]" msg | `UnknownError msg -> Format.fprintf ppf "@[<2>Unknown Error:@ %s@]" msg let pp_exn ppf exn = match of_exn exn with | Some err -> pp ppf err | None -> Format.fprintf ppf "%s" (Printexc.to_string exn) let to_string error = Format.asprintf "%a" pp error (** {1 Syntax Module} *) module Syntax = struct let ( let* ) x f = bind f x let ( let+ ) x f = map f x let ( and* ) = both end