(*--------------------------------------------------------------------------- Copyright (c) 2025 Anil Madhavapeddy . All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (** Centralized error handling for the Requests library using Eio.Io exceptions. This module follows the Eio.Io exception pattern for structured error handling, providing granular error types and query functions for smart retry logic. {2 Usage} Errors are raised using the Eio.Io pattern: {[ raise (Error.err (Error.Timeout { operation = "connect"; duration = Some 30.0 })) ]} To catch and handle errors: {[ try (* ... HTTP request ... *) with | Eio.Io (Error.E e, _) when Error.is_retryable e -> (* Retry the request *) | Eio.Io (Error.E e, _) -> Printf.eprintf "Request failed: %s\n" (Error.to_string e) ]} *) (** Log source for error reporting *) val src : Logs.Src.t (** {1 Error Type} Granular error variants with contextual information. Each variant contains a record with relevant details. *) type error = (* Timeout errors *) | Timeout of { operation: string; duration: float option } (* Redirect errors *) | Too_many_redirects of { url: string; count: int; max: int } | Invalid_redirect of { url: string; reason: string } (* HTTP response errors *) (* Note: headers stored as list to avoid dependency cycle with Headers module *) | Http_error of { url: string; status: int; reason: string; body_preview: string option; headers: (string * string) list } (* Authentication errors *) | Authentication_failed of { url: string; reason: string } (* Connection errors - granular breakdown *) | Dns_resolution_failed of { hostname: string } | Tcp_connect_failed of { host: string; port: int; reason: string } | Tls_handshake_failed of { host: string; reason: string } (* Security-related errors *) | Invalid_header of { name: string; reason: string } | Body_too_large of { limit: int64; actual: int64 option } | Headers_too_large of { limit: int; actual: int } | Decompression_bomb of { limit: int64; ratio: float } | Content_length_mismatch of { expected: int64; actual: int64 } | Insecure_auth of { url: string; auth_type: string } (** Per RFC 7617 Section 4 and RFC 6750 Section 5.1: Basic, Bearer, and Digest authentication over unencrypted HTTP exposes credentials to eavesdropping. Raised when attempting to use these auth methods over HTTP without explicit opt-out. *) (* JSON errors *) | Json_parse_error of { body_preview: string; reason: string } | Json_encode_error of { reason: string } (* Other errors *) | Proxy_error of { host: string; reason: string } | Encoding_error of { encoding: string; reason: string } | Invalid_url of { url: string; reason: string } | Invalid_request of { reason: string } (** {1 Eio.Exn Integration} *) (** Extension of [Eio.Exn.err] for Requests errors *) type Eio.Exn.err += E of error (** Create an Eio exception from an error. Usage: [raise (err (Timeout { operation = "read"; duration = Some 5.0 }))] *) val err : error -> exn (** {1 URL and Credential Sanitization} *) (** Remove userinfo (username:password) from a URL for safe logging *) val sanitize_url : string -> string (** Redact sensitive headers (Authorization, Cookie, etc.) for safe logging. Takes and returns a list of (name, value) pairs. *) val sanitize_headers : (string * string) list -> (string * string) list (** Check if a header name is sensitive (case-insensitive) *) val is_sensitive_header : string -> bool (** {1 Pretty Printing} *) (** Pretty printer for error values *) val pp_error : Format.formatter -> error -> unit (** {1 Query Functions} These functions enable smart error handling without pattern matching. *) (** Returns [true] if the error is a timeout *) val is_timeout : error -> bool (** Returns [true] if the error is a DNS resolution failure *) val is_dns : error -> bool (** Returns [true] if the error is a TLS handshake failure *) val is_tls : error -> bool (** Returns [true] if the error is any connection-related failure (DNS, TCP connect, or TLS handshake) *) val is_connection : error -> bool (** Returns [true] if the error is an HTTP response error *) val is_http_error : error -> bool (** Returns [true] if the error is a client error (4xx status or similar) *) val is_client_error : error -> bool (** Returns [true] if the error is a server error (5xx status) *) val is_server_error : error -> bool (** Returns [true] if the error is typically retryable. Retryable errors include: timeouts, connection errors, and certain HTTP status codes (408, 429, 500, 502, 503, 504) *) val is_retryable : error -> bool (** Returns [true] if the error is security-related (header injection, body too large, decompression bomb, etc.) *) val is_security_error : error -> bool (** Returns [true] if the error is a JSON parsing or encoding error *) val is_json_error : error -> bool (** {1 Error Extraction} *) (** Extract error from an Eio.Io exception, if it's a Requests error *) val of_eio_exn : exn -> error option (** {1 HTTP Status Helpers} *) (** Get the HTTP status code from an error, if applicable *) val get_http_status : error -> int option (** Get the URL associated with an error, if applicable *) val get_url : error -> string option (** {1 String Conversion} *) (** Convert error to human-readable string *) val to_string : error -> string