A batteries included HTTP/1.1 client in OCaml
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(lint): rename constructors to v and resolve E332/E331/E410 across packages

Rename create/make to v in rpmsg, error, proxy, auth, version, and
other modules. Fix doc style issues in error.mli, requests.mli, and
expect_continue.mli. Update all callers.

+63 -66
+4 -4
examples/session_example.ml
··· 11 11 Switch.run @@ fun sw -> 12 12 (* Example 1: Basic GET request *) 13 13 Printf.printf "\n=== Example 1: Basic GET Request ===\n%!"; 14 - let req = Requests.create ~sw env in 14 + let req = Requests.v ~sw env in 15 15 let resp1 = Requests.get req "https://httpbin.org/get" in 16 16 Printf.printf "Status: %d\n%!" (Requests.Response.status_code resp1); 17 17 let body1 = ··· 55 55 56 56 (* Example 4: Session with default headers *) 57 57 Printf.printf "\n=== Example 4: Session with Default Headers ===\n%!"; 58 - let req2 = Requests.create ~sw env in 58 + let req2 = Requests.v ~sw env in 59 59 let req2 = 60 60 Requests.set_default_header req2 "User-Agent" "OCaml-Requests/1.0" 61 61 in ··· 89 89 let retry_config = 90 90 Requests.Retry.config ~max_retries:3 ~backoff_factor:0.5 () 91 91 in 92 - let req_with_retry = Requests.create ~sw ~retry:retry_config env in 92 + let req_with_retry = Requests.v ~sw ~retry:retry_config env in 93 93 let req_with_retry = 94 94 Requests.set_timeout req_with_retry (Requests.Timeout.v ~total:10.0 ()) 95 95 in ··· 143 143 144 144 (* Example 11: Timeouts *) 145 145 Printf.printf "\n=== Example 11: Timeouts ===\n%!"; 146 - let req_timeout = Requests.create ~sw env in 146 + let req_timeout = Requests.v ~sw env in 147 147 let req_timeout = 148 148 Requests.set_timeout req_timeout (Requests.Timeout.v ~total:5.0 ()) 149 149 in
+2 -2
lib/core/error.ml
··· 15 15 Following the Eio.Io exception pattern for structured error handling. Each 16 16 variant contains a record with contextual information. *) 17 17 18 - type error = 18 + type t = 19 19 (* Timeout errors *) 20 20 | Timeout of { operation : string; duration : float option } 21 21 (* Redirect errors *) ··· 235 235 236 236 Following the pattern from ocaml-conpool for structured Eio exceptions *) 237 237 238 - type Eio.Exn.err += E of error 238 + type Eio.Exn.err += E of t 239 239 240 240 let err e = Eio.Exn.create (E e) 241 241
+25 -25
lib/core/error.mli
··· 37 37 Granular error variants with contextual information. Each variant contains a 38 38 record with relevant details. *) 39 39 40 - type error = 40 + type t = 41 41 (* Timeout errors *) 42 42 | Timeout of { operation : string; duration : float option } 43 43 (* Redirect errors *) ··· 123 123 (** {1 Eio.Exn Integration} *) 124 124 125 125 (** Extension of [Eio.Exn.err] for Requests errors *) 126 - type Eio.Exn.err += E of error 126 + type Eio.Exn.err += E of t 127 127 128 - val err : error -> exn 128 + val err : t -> exn 129 129 (** [err e] creates an Eio exception from an error. *) 130 130 131 131 (** {1 URL and Credential Sanitization} *) ··· 142 142 143 143 (** {1 Pretty Printing} *) 144 144 145 - val pp_error : Format.formatter -> error -> unit 145 + val pp_error : Format.formatter -> t -> unit 146 146 (** Pretty printer for error values. *) 147 147 148 148 (** {1 Query Functions} 149 149 150 150 These functions enable smart error handling without pattern matching. *) 151 151 152 - val is_timeout : error -> bool 152 + val is_timeout : t -> bool 153 153 (** [is_timeout e] returns [true] if [e] is a timeout. *) 154 154 155 - val is_dns : error -> bool 155 + val is_dns : t -> bool 156 156 (** [is_dns e] returns [true] if [e] is a DNS resolution failure. *) 157 157 158 - val is_tls : error -> bool 158 + val is_tls : t -> bool 159 159 (** [is_tls e] returns [true] if [e] is a TLS handshake failure. *) 160 160 161 - val is_connection : error -> bool 161 + val is_connection : t -> bool 162 162 (** [is_connection e] returns [true] if [e] is any connection-related failure 163 163 (DNS, TCP connect, or TLS handshake). *) 164 164 165 - val is_http_error : error -> bool 165 + val is_http_error : t -> bool 166 166 (** [is_http_error e] returns [true] if [e] is an HTTP response error. *) 167 167 168 - val is_client_error : error -> bool 168 + val is_client_error : t -> bool 169 169 (** [is_client_error e] returns [true] if [e] is a client error (4xx status or 170 170 similar). *) 171 171 172 - val is_server_error : error -> bool 172 + val is_server_error : t -> bool 173 173 (** [is_server_error e] returns [true] if [e] is a server error (5xx status). *) 174 174 175 - val is_retryable : error -> bool 175 + val is_retryable : t -> bool 176 176 (** [is_retryable e] returns [true] if [e] is typically retryable. Retryable 177 177 errors include: timeouts, connection errors, and certain HTTP status codes 178 178 (408, 429, 500, 502, 503, 504). *) 179 179 180 - val is_security_error : error -> bool 180 + val is_security_error : t -> bool 181 181 (** [is_security_error e] returns [true] if [e] is security-related (header 182 182 injection, body too large, decompression bomb, etc.). *) 183 183 184 - val is_json_error : error -> bool 184 + val is_json_error : t -> bool 185 185 (** [is_json_error e] returns [true] if [e] is a JSON parsing or encoding error. 186 186 *) 187 187 188 - val is_oauth_error : error -> bool 188 + val is_oauth_error : t -> bool 189 189 (** [is_oauth_error e] returns [true] if [e] is an OAuth-related error 190 190 (Oauth_error, Token_refresh_failed, Token_expired). *) 191 191 192 192 (** {1 Error Extraction} *) 193 193 194 - val of_eio_exn : exn -> error option 194 + val of_eio_exn : exn -> t option 195 195 (** Extract error from an Eio.Io exception, if it's a Requests error. *) 196 196 197 197 (** {1 HTTP Status Helpers} *) 198 198 199 - val http_status : error -> int option 199 + val http_status : t -> int option 200 200 (** Get the HTTP status code from an error, if applicable. *) 201 201 202 - val url : error -> string option 202 + val url : t -> string option 203 203 (** Get the URL associated with an error, if applicable. *) 204 204 205 205 (** {1 String Conversion} *) 206 206 207 - val to_string : error -> string 207 + val to_string : t -> string 208 208 (** Convert error to human-readable string. *) 209 209 210 210 (** {1 Convenience Constructors} ··· 313 313 Query functions for HTTP/2 specific errors per 314 314 {{:https://datatracker.ietf.org/doc/html/rfc9113}RFC 9113}. *) 315 315 316 - val is_h2_error : error -> bool 316 + val is_h2_error : t -> bool 317 317 (** [is_h2_error e] returns [true] if [e] is any HTTP/2 protocol error. *) 318 318 319 - val is_h2_connection_error : error -> bool 319 + val is_h2_connection_error : t -> bool 320 320 (** [is_h2_connection_error e] returns [true] if [e] is an HTTP/2 321 321 connection-level error. Connection errors terminate the entire HTTP/2 322 322 connection. *) 323 323 324 - val is_h2_stream_error : error -> bool 324 + val is_h2_stream_error : t -> bool 325 325 (** [is_h2_stream_error e] returns [true] if [e] is an HTTP/2 stream-level 326 326 error. Stream errors only affect a single stream. *) 327 327 328 - val is_h2_retryable : error -> bool 328 + val is_h2_retryable : t -> bool 329 329 (** [is_h2_retryable e] returns [true] if the HTTP/2 error is typically 330 330 retryable. Retryable errors include: 331 331 - GOAWAY with NO_ERROR (graceful shutdown) 332 332 - REFUSED_STREAM (server didn't process the request) 333 333 - ENHANCE_YOUR_CALM (after backoff). *) 334 334 335 - val h2_error_code : error -> int32 option 335 + val h2_error_code : t -> int32 option 336 336 (** Get the HTTP/2 error code from an error, if applicable. Error codes are 337 337 defined in RFC 9113 Section 7. *) 338 338 339 - val h2_stream_id : error -> int32 option 339 + val h2_stream_id : t -> int32 option 340 340 (** Get the stream ID associated with an HTTP/2 error, if applicable. *) 341 341 342 342 (** {1 HTTP/2 Error Constructors}
+1 -1
lib/core/expect_continue.mli
··· 15 15 {[ 16 16 (* Use 100-continue for bodies >= 1MB (default) *) 17 17 let session = 18 - Requests.create ~sw ~expect_100_continue:(`Threshold 1_048_576L) env 18 + Requests.v ~sw ~expect_100_continue:(`Threshold 1_048_576L) env 19 19 20 20 (* Always use 100-continue *) 21 21 let session = Requests.v ~sw ~expect_100_continue:`Always env
+1 -1
lib/core/response.mli
··· 281 281 282 282 @raise Eio.Io with [Error.Http_error] if status code >= 400. *) 283 283 284 - val check_status : t -> (t, Error.error) result 284 + val check_status : t -> (t, Error.t) result 285 285 (** [check_status response] returns [Ok response] if the status code is < 400, 286 286 or [Error error] if the status code indicates an error (>= 400). 287 287
+2 -2
lib/core/version.ml
··· 8 8 debugging. *) 9 9 10 10 (** Library version - update this when releasing new versions *) 11 - let version = "0.1.0" 11 + let v = "0.1.0" 12 12 13 13 (** Library name *) 14 14 let name = "ocaml-requests" ··· 16 16 (** Default User-Agent header value. Format follows common conventions: 17 17 library-name/version (runtime-info) Per RFC 9110 Section 10.1.5, this helps 18 18 with debugging and statistics. *) 19 - let user_agent = Fmt.str "%s/%s (OCaml %s)" name version Sys.ocaml_version 19 + let user_agent = Fmt.str "%s/%s (OCaml %s)" name v Sys.ocaml_version
+1 -1
lib/core/version.mli
··· 7 7 and Recommendation #30: Provides a default User-Agent header for HTTP 8 8 requests. *) 9 9 10 - val version : string 10 + val v : string 11 11 (** Library version string (e.g., "0.1.0"). *) 12 12 13 13 val name : string
+4 -5
lib/features/auth.ml
··· 41 41 match Uri.scheme uri with Some "https" -> true | _ -> false 42 42 43 43 (** Get the authentication type name for error messages *) 44 - let auth_type_name = function 44 + let type_name = function 45 45 | No_auth -> "None" 46 46 | Basic _ -> "Basic" 47 47 | Bearer _ -> "Bearer" ··· 70 70 if allow_insecure_auth then 71 71 Log.warn (fun m -> 72 72 m "allow_insecure_auth=true: skipping HTTPS check for %s auth" 73 - (auth_type_name auth)) 73 + (type_name auth)) 74 74 else if requires_https auth && not (is_https url) then begin 75 75 Log.err (fun m -> 76 76 m 77 77 "%s authentication rejected over HTTP (use HTTPS or \ 78 78 allow_insecure_auth=true)" 79 - (auth_type_name auth)); 80 - raise 81 - (Error.err (Error.Insecure_auth { url; auth_type = auth_type_name auth })) 79 + (type_name auth)); 80 + raise (Error.err (Error.Insecure_auth { url; auth_type = type_name auth })) 82 81 end 83 82 84 83 let apply auth headers =
+9 -9
lib/features/proxy.ml
··· 14 14 15 15 (** {1 Proxy Types} *) 16 16 17 - type proxy_type = HTTP | SOCKS5 17 + type kind = HTTP | SOCKS5 18 18 19 19 type config = { 20 20 host : string; 21 21 port : int; 22 - proxy_type : proxy_type; 22 + kind : kind; 23 23 auth : Auth.t option; 24 24 no_proxy : string list; 25 25 } ··· 28 28 29 29 let http ?(port = 8080) ?auth ?(no_proxy = []) host = 30 30 Log.debug (fun m -> m "Creating HTTP proxy config: %s:%d" host port); 31 - { host; port; proxy_type = HTTP; auth; no_proxy } 31 + { host; port; kind = HTTP; auth; no_proxy } 32 32 33 33 let socks5 ?(port = 1080) ?auth ?(no_proxy = []) host = 34 34 Log.debug (fun m -> m "Creating SOCKS5 proxy config: %s:%d" host port); 35 - { host; port; proxy_type = SOCKS5; auth; no_proxy } 35 + { host; port; kind = SOCKS5; auth; no_proxy } 36 36 37 37 (** {1 Pattern Matching for NO_PROXY} *) 38 38 ··· 87 87 implemented. 88 88 @raise Error.Proxy_error if SOCKS5 is requested *) 89 89 let validate_supported config = 90 - match config.proxy_type with 90 + match config.kind with 91 91 | HTTP -> () 92 92 | SOCKS5 -> 93 93 Log.err (fun m -> m "SOCKS5 proxy requested but not implemented"); ··· 152 152 | Some url -> 153 153 let host, port, auth = parse_proxy_url url in 154 154 Log.info (fun m -> m "Proxy configured from environment: %s:%d" host port); 155 - Some { host; port; proxy_type = HTTP; auth; no_proxy } 155 + Some { host; port; kind = HTTP; auth; no_proxy } 156 156 | None -> 157 157 Log.debug (fun m -> m "No proxy configured in environment"); 158 158 None ··· 189 189 let host, port, auth = parse_proxy_url purl in 190 190 Log.debug (fun m -> 191 191 m "Using proxy %s:%d for URL %s" host port (Error.sanitize_url url)); 192 - Some { host; port; proxy_type = HTTP; auth; no_proxy } 192 + Some { host; port; kind = HTTP; auth; no_proxy } 193 193 | None -> None 194 194 195 195 (** {1 Pretty Printing} *) 196 196 197 - let pp_proxy_type ppf = function 197 + let pp_kind ppf = function 198 198 | HTTP -> Fmt.pf ppf "HTTP" 199 199 | SOCKS5 -> Fmt.pf ppf "SOCKS5" 200 200 201 201 let pp_config ppf config = 202 202 Fmt.pf ppf "@[<v>Proxy Configuration:@,"; 203 - Fmt.pf ppf " Type: %a@," pp_proxy_type config.proxy_type; 203 + Fmt.pf ppf " Type: %a@," pp_kind config.kind; 204 204 Fmt.pf ppf " Host: %s@," config.host; 205 205 Fmt.pf ppf " Port: %d@," config.port; 206 206 Fmt.pf ppf " Auth: %s@,"
+3 -3
lib/features/proxy.mli
··· 40 40 (** {1 Proxy Types} *) 41 41 42 42 (** Proxy protocol type *) 43 - type proxy_type = 43 + type kind = 44 44 | HTTP (** HTTP proxy (CONNECT for HTTPS, absolute-URI for HTTP) *) 45 45 | SOCKS5 (** SOCKS5 proxy (RFC 1928) - future extension *) 46 46 47 47 type config = { 48 48 host : string; (** Proxy server hostname *) 49 49 port : int; (** Proxy server port (default: 8080) *) 50 - proxy_type : proxy_type; 50 + kind : kind; 51 51 auth : Auth.t option; (** Proxy authentication (Proxy-Authorization) *) 52 52 no_proxy : string list; (** Hosts/patterns to bypass proxy *) 53 53 } ··· 128 128 129 129 (** {1 Pretty Printing} *) 130 130 131 - val pp_proxy_type : Format.formatter -> proxy_type -> unit 131 + val pp_kind : Format.formatter -> kind -> unit 132 132 (** Pretty printer for proxy type. *) 133 133 134 134 val pp_config : Format.formatter -> config -> unit
+9 -9
lib/requests.mli
··· 20 20 21 21 {3 Session API (Requests.t)} 22 22 23 - Use {!Requests.create} when you need: 23 + Use {!Requests.v} when you need: 24 24 - {b Cookie persistence} across requests (automatic session handling) 25 25 - {b Connection pooling} for efficient reuse of TCP/TLS connections 26 26 - {b Shared authentication} configured once for all requests ··· 36 36 run @@ fun env -> 37 37 Eio.Switch.run @@ fun sw -> 38 38 (* Create a session with connection pooling *) 39 - let session = Requests.create ~sw env in 39 + let session = Requests.v ~sw env in 40 40 41 41 (* Configure authentication once for all requests *) 42 42 let session = ··· 164 164 ~max_retries:3 165 165 ~status_forcelist:[429; 500; 502; 503; 504] (* Retry these codes *) 166 166 () in 167 - let req = Requests.create ~sw ~retry:retry_config env in 167 + let req = Requests.v ~sw ~retry:retry_config env in 168 168 ]} 169 169 170 170 {b Catching Exceptions:} ··· 343 343 {b Only set to [true] for local development or testing environments.} 344 344 Example for local dev server: 345 345 {[ 346 - let session = Requests.create ~sw ~allow_insecure_auth:true env in 346 + let session = Requests.v ~sw ~allow_insecure_auth:true env in 347 347 let session = Requests.set_auth session (Requests.Auth.basic ~username:"dev" ~password:"dev") in 348 348 (* Can now make requests to http://localhost:8080 with Basic auth *) 349 349 ]} *) ··· 377 377 378 378 Example of concurrent requests using Fiber.both: 379 379 {[ 380 - let req = Requests.create ~sw env in 380 + let req = Requests.v ~sw env in 381 381 382 382 (* Use Fiber.both for two concurrent requests *) 383 383 let (r1, r2) = Eio.Fiber.both ··· 392 392 393 393 Example using Fiber.all for multiple requests: 394 394 {[ 395 - let req = Requests.create ~sw env in 395 + let req = Requests.v ~sw env in 396 396 397 397 (* Use Fiber.all for multiple concurrent requests *) 398 398 let urls = ··· 424 424 425 425 Example using Promise for concurrent requests with individual control: 426 426 {[ 427 - let req = Requests.create ~sw env in 427 + let req = Requests.v ~sw env in 428 428 429 429 (* Start requests in parallel using promises *) 430 430 let p1, r1 = Eio.Promise.create () in ··· 746 746 let config_t = Requests.Cmd.config_term "myapp" env#fs in 747 747 let main config = 748 748 Eio.Switch.run @@ fun sw -> 749 - let req = Requests.Cmd.create config env sw in 749 + let req = Requests.Cmd.v config env sw in 750 750 (* Use requests *) 751 751 in 752 752 let cmd = Cmd.v info Term.(const main $ config_t) in ··· 798 798 let minimal_t = Requests.Cmd.minimal_term "myapp" env#fs in 799 799 let main (xdg, persist) = 800 800 Eio.Switch.run @@ fun sw -> 801 - let req = Requests.create ~sw ~xdg ~persist_cookies:persist env in 801 + let req = Requests.v ~sw ~xdg ~persist_cookies:persist env in 802 802 (* Use requests *) 803 803 in 804 804 let cmd = Cmd.v info Term.(const main $ minimal_t) in
+2 -4
test/test_version.ml
··· 10 10 (** {1 Version String} *) 11 11 12 12 let test_version_non_empty () = 13 - Alcotest.(check bool) 14 - "version is non-empty" true 15 - (String.length Version.version > 0) 13 + Alcotest.(check bool) "version is non-empty" true (String.length Version.v > 0) 16 14 17 15 (** {1 Name} *) 18 16 ··· 38 36 Alcotest.(check bool) 39 37 "user_agent contains version" true 40 38 (let ua = Version.user_agent in 41 - let ver = Version.version in 39 + let ver = Version.v in 42 40 try 43 41 let rec find i = 44 42 if i + String.length ver > String.length ua then raise Not_found