forked from
anil.recoil.org/ocaml-requests
A batteries included HTTP/1.1 client in OCaml
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** Centralized error handling for the Requests library using Eio.Io exceptions.
7
8 This module follows the Eio.Io exception pattern for structured error handling,
9 providing granular error types and query functions for smart retry logic.
10
11 {2 Usage}
12
13 Errors are raised using the Eio.Io pattern:
14 {[
15 raise (Error.err (Error.Timeout { operation = "connect"; duration = Some 30.0 }))
16 ]}
17
18 To catch and handle errors:
19 {[
20 try
21 (* ... HTTP request ... *)
22 with
23 | Eio.Io (Error.E e, _) when Error.is_retryable e ->
24 (* Retry the request *)
25 | Eio.Io (Error.E e, _) ->
26 Printf.eprintf "Request failed: %s\n" (Error.to_string e)
27 ]}
28*)
29
30(** Log source for error reporting *)
31val src : Logs.Src.t
32
33(** {1 Error Type}
34
35 Granular error variants with contextual information.
36 Each variant contains a record with relevant details. *)
37
38type error =
39 (* Timeout errors *)
40 | Timeout of { operation: string; duration: float option }
41
42 (* Redirect errors *)
43 | Too_many_redirects of { url: string; count: int; max: int }
44 | Invalid_redirect of { url: string; reason: string }
45
46 (* HTTP response errors *)
47 (* Note: headers stored as list to avoid dependency cycle with Headers module *)
48 | Http_error of {
49 url: string;
50 status: int;
51 reason: string;
52 body_preview: string option;
53 headers: (string * string) list
54 }
55
56 (* Authentication errors *)
57 | Authentication_failed of { url: string; reason: string }
58
59 (* Connection errors - granular breakdown *)
60 | Dns_resolution_failed of { hostname: string }
61 | Tcp_connect_failed of { host: string; port: int; reason: string }
62 | Tls_handshake_failed of { host: string; reason: string }
63
64 (* Security-related errors *)
65 | Invalid_header of { name: string; reason: string }
66 | Body_too_large of { limit: int64; actual: int64 option }
67 | Headers_too_large of { limit: int; actual: int }
68 | Decompression_bomb of { limit: int64; ratio: float }
69 | Content_length_mismatch of { expected: int64; actual: int64 }
70 | Insecure_auth of { url: string; auth_type: string }
71 (** Per RFC 7617 Section 4 and RFC 6750 Section 5.1:
72 Basic, Bearer, and Digest authentication over unencrypted HTTP
73 exposes credentials to eavesdropping. Raised when attempting
74 to use these auth methods over HTTP without explicit opt-out. *)
75
76 (* JSON errors *)
77 | Json_parse_error of { body_preview: string; reason: string }
78 | Json_encode_error of { reason: string }
79
80 (* Other errors *)
81 | Proxy_error of { host: string; reason: string }
82 | Encoding_error of { encoding: string; reason: string }
83 | Invalid_url of { url: string; reason: string }
84 | Invalid_request of { reason: string }
85
86(** {1 Eio.Exn Integration} *)
87
88(** Extension of [Eio.Exn.err] for Requests errors *)
89type Eio.Exn.err += E of error
90
91(** Create an Eio exception from an error.
92 Usage: [raise (err (Timeout { operation = "read"; duration = Some 5.0 }))] *)
93val err : error -> exn
94
95(** {1 URL and Credential Sanitization} *)
96
97(** Remove userinfo (username:password) from a URL for safe logging *)
98val sanitize_url : string -> string
99
100(** Redact sensitive headers (Authorization, Cookie, etc.) for safe logging.
101 Takes and returns a list of (name, value) pairs. *)
102val sanitize_headers : (string * string) list -> (string * string) list
103
104(** Check if a header name is sensitive (case-insensitive) *)
105val is_sensitive_header : string -> bool
106
107(** {1 Pretty Printing} *)
108
109(** Pretty printer for error values *)
110val pp_error : Format.formatter -> error -> unit
111
112(** {1 Query Functions}
113
114 These functions enable smart error handling without pattern matching. *)
115
116(** Returns [true] if the error is a timeout *)
117val is_timeout : error -> bool
118
119(** Returns [true] if the error is a DNS resolution failure *)
120val is_dns : error -> bool
121
122(** Returns [true] if the error is a TLS handshake failure *)
123val is_tls : error -> bool
124
125(** Returns [true] if the error is any connection-related failure
126 (DNS, TCP connect, or TLS handshake) *)
127val is_connection : error -> bool
128
129(** Returns [true] if the error is an HTTP response error *)
130val is_http_error : error -> bool
131
132(** Returns [true] if the error is a client error (4xx status or similar) *)
133val is_client_error : error -> bool
134
135(** Returns [true] if the error is a server error (5xx status) *)
136val is_server_error : error -> bool
137
138(** Returns [true] if the error is typically retryable.
139 Retryable errors include: timeouts, connection errors,
140 and certain HTTP status codes (408, 429, 500, 502, 503, 504) *)
141val is_retryable : error -> bool
142
143(** Returns [true] if the error is security-related
144 (header injection, body too large, decompression bomb, etc.) *)
145val is_security_error : error -> bool
146
147(** Returns [true] if the error is a JSON parsing or encoding error *)
148val is_json_error : error -> bool
149
150(** {1 Error Extraction} *)
151
152(** Extract error from an Eio.Io exception, if it's a Requests error *)
153val of_eio_exn : exn -> error option
154
155(** {1 HTTP Status Helpers} *)
156
157(** Get the HTTP status code from an error, if applicable *)
158val get_http_status : error -> int option
159
160(** Get the URL associated with an error, if applicable *)
161val get_url : error -> string option
162
163(** {1 String Conversion} *)
164
165(** Convert error to human-readable string *)
166val to_string : error -> string