My agentic slop goes here. Not intended for anyone else!

more

+3 -3
stack/requests/bin/dune
···
-
(executable
-
(public_name ocurl)
-
(name ocurl)
+
(executables
+
(public_names ocurl)
+
(names ocurl)
(libraries requests eio_main cmdliner logs logs.fmt fmt.tty yojson))
+3 -27
stack/requests/bin/ocurl.ml
···
| None, None -> None, headers
in
-
(* Convert method to Requests.Method.t *)
-
let req_method = match method_ with
-
| `GET -> Requests.Method.GET
-
| `POST -> Requests.Method.POST
-
| `PUT -> Requests.Method.PUT
-
| `DELETE -> Requests.Method.DELETE
-
| `HEAD -> Requests.Method.HEAD
-
| `OPTIONS -> Requests.Method.OPTIONS
-
| `PATCH -> Requests.Method.PATCH
-
in
+
(* Convert to full Method.t type *)
+
let req_method : Requests.Method.t = (method_ :> Requests.Method.t) in
(* Convert timeout float to Timeout.t *)
let timeout_obj = Option.map (fun t -> Requests.Timeout.create ~total:t ()) timeout in
···
Logs.app (fun m -> m "✓ Success")
with
-
| Requests.TimeoutError ->
-
if not quiet then
-
Logs.err (fun m -> m "Request timed out");
-
exit 1
-
| Requests.TooManyRedirects { url; count } ->
-
if not quiet then
-
Logs.err (fun m -> m "Too many redirects (%d) following %s" count url);
-
exit 1
-
| Requests.ConnectionError msg ->
-
if not quiet then
-
Logs.err (fun m -> m "Connection error: %s" msg);
-
exit 1
-
| Requests.HTTPError { status; body; _ } ->
-
if not quiet then
-
Logs.err (fun m -> m "HTTP error %d: %s" status body);
-
exit 1
| exn ->
if not quiet then
-
Logs.err (fun m -> m "Unexpected error: %s" (Printexc.to_string exn));
+
Logs.err (fun m -> m "%a" Requests.Error.pp_exn exn);
exit 1
) urls
-1
stack/requests/lib/body.ml
···
(* Complex to calculate, handled during sending *)
None
-
(* Convert Body.t to Cohttp body - internal use only *)
let to_cohttp_body = function
| Empty -> None
| String { content; _ } -> Some (Cohttp_eio.Body.of_string content)
+2 -1
stack/requests/lib/body.mli
···
val content_length : t -> int64 option
(** Get content length if known *)
-
(** Internal use - not part of public API *)
+
(** Internal conversion for cohttp-eio integration *)
val to_cohttp_body : t -> Cohttp_eio.Body.t option
+
(** Convert body to cohttp-eio body format *)
+171
stack/requests/lib/error.ml
···
+
(** Centralized error handling for the Requests library *)
+
+
(** {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 "@[<v>@[<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
+124
stack/requests/lib/error.mli
···
+
(** Centralized error handling for the Requests library *)
+
+
(** {1 Exception Types} *)
+
+
(** Raised when a request times out *)
+
exception Timeout
+
+
(** Raised when too many redirects are encountered *)
+
exception TooManyRedirects of { url: string; count: int; max: int }
+
+
(** Raised when a connection error occurs *)
+
exception ConnectionError of string
+
+
(** Raised when an HTTP error response is received *)
+
exception HTTPError of {
+
url: string;
+
status: int;
+
reason: string;
+
body: string option;
+
headers: Headers.t
+
}
+
+
(** Raised when authentication fails *)
+
exception AuthenticationError of string
+
+
(** Raised when there's an SSL/TLS error *)
+
exception SSLError of string
+
+
(** Raised when proxy connection fails *)
+
exception ProxyError of string
+
+
(** Raised when content encoding/decoding fails *)
+
exception EncodingError of string
+
+
(** Raised when an invalid URL is provided *)
+
exception InvalidURL of string
+
+
(** Raised when request is invalid *)
+
exception InvalidRequest of string
+
+
(** {1 Error Type} *)
+
+
(** Unified error type for result-based error handling *)
+
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} *)
+
+
(** Convert an exception to an error type *)
+
val of_exn : exn -> t option
+
+
(** Convert an error type to an exception *)
+
val to_exn : t -> exn
+
+
(** Raise an error as an exception *)
+
val raise : t -> 'a
+
+
(** {1 Combinators} *)
+
+
(** Wrap a function that may raise exceptions into a result type *)
+
val catch : (unit -> 'a) -> ('a, t) result
+
+
(** Wrap an async function that may raise exceptions *)
+
val catch_async : (unit -> 'a) -> ('a, t) result
+
+
(** Map over the success case of a result *)
+
val map : ('a -> 'b) -> ('a, t) result -> ('b, t) result
+
+
(** Bind for result types with error *)
+
val bind : ('a -> ('b, t) result) -> ('a, t) result -> ('b, t) result
+
+
(** Applicative operator for combining results *)
+
val both : ('a, t) result -> ('b, t) result -> ('a * 'b, t) result
+
+
(** Get value or raise the error *)
+
val get_exn : ('a, t) result -> 'a
+
+
(** Get value or use default *)
+
val get_or : default:'a -> ('a, t) result -> 'a
+
+
(** Check if error is retryable *)
+
val is_retryable : t -> bool
+
+
(** Check if error is a client error (4xx) *)
+
val is_client_error : t -> bool
+
+
(** Check if error is a server error (5xx) *)
+
val is_server_error : t -> bool
+
+
(** {1 Pretty Printing} *)
+
+
(** Pretty printer for errors *)
+
val pp : Format.formatter -> t -> unit
+
+
(** Pretty printer for exceptions (falls back to Printexc if not a known exception) *)
+
val pp_exn : Format.formatter -> exn -> unit
+
+
(** Convert error to string *)
+
val to_string : t -> string
+
+
(** {1 Syntax Module} *)
+
+
(** Syntax module for let-operators *)
+
module Syntax : sig
+
(** Bind operator for result types *)
+
val ( let* ) : ('a, t) result -> ('a -> ('b, t) result) -> ('b, t) result
+
+
(** Map operator for result types *)
+
val ( let+ ) : ('a, t) result -> ('a -> 'b) -> ('b, t) result
+
+
(** Both operator for combining results *)
+
val ( and* ) : ('a, t) result -> ('b, t) result -> ('a * 'b, t) result
+
end
+56 -26
stack/requests/lib/method.ml
···
-
type t = GET | POST | PUT | DELETE | HEAD | OPTIONS | PATCH | CONNECT | TRACE
+
type t = [
+
| `GET
+
| `POST
+
| `PUT
+
| `DELETE
+
| `HEAD
+
| `OPTIONS
+
| `PATCH
+
| `CONNECT
+
| `TRACE
+
| `Other of string
+
]
let to_string = function
-
| GET -> "GET"
-
| POST -> "POST"
-
| PUT -> "PUT"
-
| DELETE -> "DELETE"
-
| HEAD -> "HEAD"
-
| OPTIONS -> "OPTIONS"
-
| PATCH -> "PATCH"
-
| CONNECT -> "CONNECT"
-
| TRACE -> "TRACE"
+
| `GET -> "GET"
+
| `POST -> "POST"
+
| `PUT -> "PUT"
+
| `DELETE -> "DELETE"
+
| `HEAD -> "HEAD"
+
| `OPTIONS -> "OPTIONS"
+
| `PATCH -> "PATCH"
+
| `CONNECT -> "CONNECT"
+
| `TRACE -> "TRACE"
+
| `Other s -> String.uppercase_ascii s
let of_string s =
match String.uppercase_ascii s with
-
| "GET" -> Some GET
-
| "POST" -> Some POST
-
| "PUT" -> Some PUT
-
| "DELETE" -> Some DELETE
-
| "HEAD" -> Some HEAD
-
| "OPTIONS" -> Some OPTIONS
-
| "PATCH" -> Some PATCH
-
| "CONNECT" -> Some CONNECT
-
| "TRACE" -> Some TRACE
-
| _ -> None
+
| "GET" -> `GET
+
| "POST" -> `POST
+
| "PUT" -> `PUT
+
| "DELETE" -> `DELETE
+
| "HEAD" -> `HEAD
+
| "OPTIONS" -> `OPTIONS
+
| "PATCH" -> `PATCH
+
| "CONNECT" -> `CONNECT
+
| "TRACE" -> `TRACE
+
| other -> `Other other
+
+
let pp ppf m = Format.fprintf ppf "%s" (to_string m)
let is_safe = function
-
| GET | HEAD | OPTIONS | TRACE -> true
-
| _ -> false
+
| `GET | `HEAD | `OPTIONS | `TRACE -> true
+
| `POST | `PUT | `DELETE | `PATCH | `CONNECT | `Other _ -> false
let is_idempotent = function
-
| GET | HEAD | PUT | DELETE | OPTIONS | TRACE -> true
-
| _ -> false
+
| `GET | `HEAD | `PUT | `DELETE | `OPTIONS | `TRACE -> true
+
| `POST | `PATCH | `CONNECT | `Other _ -> false
let has_request_body = function
-
| POST | PUT | PATCH -> true
-
| _ -> false
+
| `POST | `PUT | `PATCH -> true
+
| `GET | `HEAD | `DELETE | `OPTIONS | `CONNECT | `TRACE -> false
+
| `Other _ -> false (* Conservative default for unknown methods *)
+
+
let is_cacheable = function
+
| `GET | `HEAD -> true
+
| `POST -> true (* POST can be cacheable with explicit headers *)
+
| `PUT | `DELETE | `PATCH | `OPTIONS | `CONNECT | `TRACE | `Other _ -> false
+
+
let equal m1 m2 =
+
match m1, m2 with
+
| `Other s1, `Other s2 -> String.equal (String.uppercase_ascii s1) (String.uppercase_ascii s2)
+
| m1, m2 -> m1 = m2
+
+
let compare m1 m2 =
+
match m1, m2 with
+
| `Other s1, `Other s2 -> String.compare (String.uppercase_ascii s1) (String.uppercase_ascii s2)
+
| m1, m2 -> Stdlib.compare m1 m2
+41 -7
stack/requests/lib/method.mli
···
-
(** HTTP methods *)
+
(** HTTP methods following RFC 7231 *)
-
type t = GET | POST | PUT | DELETE | HEAD | OPTIONS | PATCH | CONNECT | TRACE
+
(** HTTP method type using polymorphic variants for better composability *)
+
type t = [
+
| `GET (** Retrieve a resource *)
+
| `POST (** Submit data to be processed *)
+
| `PUT (** Replace a resource *)
+
| `DELETE (** Delete a resource *)
+
| `HEAD (** Retrieve headers only *)
+
| `OPTIONS (** Retrieve allowed methods *)
+
| `PATCH (** Partial resource modification *)
+
| `CONNECT (** Establish tunnel to server *)
+
| `TRACE (** Echo received request *)
+
| `Other of string (** Non-standard or extension method *)
+
]
+
+
(** {1 Conversion Functions} *)
val to_string : t -> string
(** Convert method to uppercase string representation *)
-
val of_string : string -> t option
-
(** Parse method from string (case-insensitive) *)
+
val of_string : string -> t
+
(** Parse method from string (case-insensitive).
+
Returns [`Other s] for unrecognized methods. *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty printer for methods *)
+
+
(** {1 Method Properties} *)
val is_safe : t -> bool
-
(** Returns true for safe methods (GET, HEAD, OPTIONS, TRACE) *)
+
(** Returns true for safe methods (GET, HEAD, OPTIONS, TRACE).
+
Safe methods should not have side effects. *)
val is_idempotent : t -> bool
-
(** Returns true for idempotent methods (GET, HEAD, PUT, DELETE, OPTIONS, TRACE) *)
+
(** Returns true for idempotent methods (GET, HEAD, PUT, DELETE, OPTIONS, TRACE).
+
Idempotent methods can be called multiple times with the same result. *)
val has_request_body : t -> bool
-
(** Returns true for methods that typically have a request body (POST, PUT, PATCH) *)
+
(** Returns true for methods that typically have a request body (POST, PUT, PATCH) *)
+
+
val is_cacheable : t -> bool
+
(** Returns true for methods whose responses are cacheable by default (GET, HEAD, POST).
+
Note: POST is only cacheable with explicit cache headers. *)
+
+
(** {1 Comparison} *)
+
+
val equal : t -> t -> bool
+
(** Compare two methods for equality *)
+
+
val compare : t -> t -> int
+
(** Compare two methods for ordering *)
+2
stack/requests/lib/mime.ml
···
in
Printf.sprintf "%s; %s" base param_str
+
let pp ppf t = Format.fprintf ppf "%s" (to_string t)
+
let charset t =
List.assoc_opt "charset" t.parameters
+3
stack/requests/lib/mime.mli
···
val to_string : t -> string
(** Convert MIME type to string representation *)
+
val pp : Format.formatter -> t -> unit
+
(** Pretty printer for MIME types *)
+
(** Common MIME types *)
val json : t
val text : t
+2 -7
stack/requests/lib/requests.ml
···
module Body = Body
module Response = Response
module Client = Client
+
module Status = Status
+
module Error = Error
module Stream = Stream
module Session = Session
module Cookie_jar = Cookie_jar
module Retry = Retry
-
module Xdge = Xdge
-
-
(* Re-export exceptions from Stream module *)
-
exception TimeoutError = Stream.Timeout
-
exception TooManyRedirects = Stream.TooManyRedirects
-
exception ConnectionError = Stream.ConnectionError
-
exception HTTPError = Stream.HTTPError
+2 -8
stack/requests/lib/requests.mli
···
(** {1 Core Types} *)
+
module Status = Status
module Method = Method
module Mime = Mime
module Headers = Headers
···
module Body = Body
module Response = Response
module Client = Client
+
module Error = Error
(** {1 Streaming Interface} *)
···
module Session = Session
module Cookie_jar = Cookie_jar
module Retry = Retry
-
module Xdge = Xdge
-
-
(** {1 Exceptions} *)
-
-
exception TimeoutError
-
exception TooManyRedirects of { url: string; count: int }
-
exception ConnectionError of string
-
exception HTTPError of { status: int; body: string; headers: Headers.t }
+2 -2
stack/requests/lib/retry.ml
···
backoff_factor = 0.3;
backoff_max = 120.0;
status_forcelist = [408; 429; 500; 502; 503; 504];
-
allowed_methods = Method.[GET; HEAD; PUT; DELETE; OPTIONS; TRACE];
+
allowed_methods = [`GET; `HEAD; `PUT; `DELETE; `OPTIONS; `TRACE];
respect_retry_after = true;
jitter = true;
}
···
?(backoff_factor = 0.3)
?(backoff_max = 120.0)
?(status_forcelist = [408; 429; 500; 502; 503; 504])
-
?(allowed_methods = Method.[GET; HEAD; PUT; DELETE; OPTIONS; TRACE])
+
?(allowed_methods = [`GET; `HEAD; `PUT; `DELETE; `OPTIONS; `TRACE])
?(respect_retry_after = true)
?(jitter = true)
() =
+9 -9
stack/requests/lib/session.ml
···
) uri params in
Uri.to_string uri
in
-
execute_request t ?headers ?auth ?timeout ~method_:Method.GET url
+
execute_request t ?headers ?auth ?timeout ~method_:`GET url
let post t ?headers ?body ?auth ?timeout ?json ?form url =
let body, headers = match json, form, body with
···
Some body, Some headers
| _, _, body -> body, headers
in
-
execute_request t ?headers ?body ?auth ?timeout ~method_:Method.POST url
+
execute_request t ?headers ?body ?auth ?timeout ~method_:`POST url
let put t ?headers ?body ?auth ?timeout ?json url =
let body, headers = match json with
···
Some body, Some headers
| None -> body, headers
in
-
execute_request t ?headers ?body ?auth ?timeout ~method_:Method.PUT url
+
execute_request t ?headers ?body ?auth ?timeout ~method_:`PUT url
let patch t ?headers ?body ?auth ?timeout ?json url =
let body, headers = match json with
···
Some body, Some headers
| None -> body, headers
in
-
execute_request t ?headers ?body ?auth ?timeout ~method_:Method.PATCH url
+
execute_request t ?headers ?body ?auth ?timeout ~method_:`PATCH url
let delete t ?headers ?auth ?timeout url =
-
execute_request t ?headers ?auth ?timeout ~method_:Method.DELETE url
+
execute_request t ?headers ?auth ?timeout ~method_:`DELETE url
let head t ?headers ?auth ?timeout url =
-
execute_request t ?headers ?auth ?timeout ~method_:Method.HEAD url
+
execute_request t ?headers ?auth ?timeout ~method_:`HEAD url
let options t ?headers ?auth ?timeout url =
-
execute_request t ?headers ?auth ?timeout ~method_:Method.OPTIONS url
+
execute_request t ?headers ?auth ?timeout ~method_:`OPTIONS url
(** {1 Streaming Operations} *)
let upload t ?headers ?auth ?timeout ?method_ ?mime ?length ~source url =
-
let method_ = Option.value method_ ~default:Method.POST in
+
let method_ = Option.value method_ ~default:`POST in
let body = Body.of_stream ?length (Option.value mime ~default:Mime.octet_stream) source in
(* TODO: Add progress tracking wrapper around source *)
execute_request t ?headers ~body ?auth ?timeout ~method_ url
let download t ?headers ?auth ?timeout url ~sink =
-
let response = execute_request t ?headers ?auth ?timeout ~method_:Method.GET url in
+
let response = execute_request t ?headers ?auth ?timeout ~method_:`GET url in
let body = Response.body response in
(* TODO: Add progress tracking wrapper *)
Eio.Flow.copy body sink
+362
stack/requests/lib/status.ml
···
+
(** HTTP status codes following RFC 7231 and extensions *)
+
+
type informational = [
+
| `Continue
+
| `Switching_protocols
+
| `Processing
+
| `Early_hints
+
]
+
+
type success = [
+
| `OK
+
| `Created
+
| `Accepted
+
| `Non_authoritative_information
+
| `No_content
+
| `Reset_content
+
| `Partial_content
+
| `Multi_status
+
| `Already_reported
+
| `Im_used
+
]
+
+
type redirection = [
+
| `Multiple_choices
+
| `Moved_permanently
+
| `Found
+
| `See_other
+
| `Not_modified
+
| `Use_proxy
+
| `Temporary_redirect
+
| `Permanent_redirect
+
]
+
+
type client_error = [
+
| `Bad_request
+
| `Unauthorized
+
| `Payment_required
+
| `Forbidden
+
| `Not_found
+
| `Method_not_allowed
+
| `Not_acceptable
+
| `Proxy_authentication_required
+
| `Request_timeout
+
| `Conflict
+
| `Gone
+
| `Length_required
+
| `Precondition_failed
+
| `Payload_too_large
+
| `Uri_too_long
+
| `Unsupported_media_type
+
| `Range_not_satisfiable
+
| `Expectation_failed
+
| `I_m_a_teapot
+
| `Misdirected_request
+
| `Unprocessable_entity
+
| `Locked
+
| `Failed_dependency
+
| `Too_early
+
| `Upgrade_required
+
| `Precondition_required
+
| `Too_many_requests
+
| `Request_header_fields_too_large
+
| `Unavailable_for_legal_reasons
+
]
+
+
type server_error = [
+
| `Internal_server_error
+
| `Not_implemented
+
| `Bad_gateway
+
| `Service_unavailable
+
| `Gateway_timeout
+
| `Http_version_not_supported
+
| `Variant_also_negotiates
+
| `Insufficient_storage
+
| `Loop_detected
+
| `Not_extended
+
| `Network_authentication_required
+
]
+
+
type standard = [
+
| informational
+
| success
+
| redirection
+
| client_error
+
| server_error
+
]
+
+
type t = [
+
| `Code of int
+
| standard
+
]
+
+
let to_int = function
+
(* Informational *)
+
| `Continue -> 100
+
| `Switching_protocols -> 101
+
| `Processing -> 102
+
| `Early_hints -> 103
+
(* Success *)
+
| `OK -> 200
+
| `Created -> 201
+
| `Accepted -> 202
+
| `Non_authoritative_information -> 203
+
| `No_content -> 204
+
| `Reset_content -> 205
+
| `Partial_content -> 206
+
| `Multi_status -> 207
+
| `Already_reported -> 208
+
| `Im_used -> 226
+
(* Redirection *)
+
| `Multiple_choices -> 300
+
| `Moved_permanently -> 301
+
| `Found -> 302
+
| `See_other -> 303
+
| `Not_modified -> 304
+
| `Use_proxy -> 305
+
| `Temporary_redirect -> 307
+
| `Permanent_redirect -> 308
+
(* Client Error *)
+
| `Bad_request -> 400
+
| `Unauthorized -> 401
+
| `Payment_required -> 402
+
| `Forbidden -> 403
+
| `Not_found -> 404
+
| `Method_not_allowed -> 405
+
| `Not_acceptable -> 406
+
| `Proxy_authentication_required -> 407
+
| `Request_timeout -> 408
+
| `Conflict -> 409
+
| `Gone -> 410
+
| `Length_required -> 411
+
| `Precondition_failed -> 412
+
| `Payload_too_large -> 413
+
| `Uri_too_long -> 414
+
| `Unsupported_media_type -> 415
+
| `Range_not_satisfiable -> 416
+
| `Expectation_failed -> 417
+
| `I_m_a_teapot -> 418
+
| `Misdirected_request -> 421
+
| `Unprocessable_entity -> 422
+
| `Locked -> 423
+
| `Failed_dependency -> 424
+
| `Too_early -> 425
+
| `Upgrade_required -> 426
+
| `Precondition_required -> 428
+
| `Too_many_requests -> 429
+
| `Request_header_fields_too_large -> 431
+
| `Unavailable_for_legal_reasons -> 451
+
(* Server Error *)
+
| `Internal_server_error -> 500
+
| `Not_implemented -> 501
+
| `Bad_gateway -> 502
+
| `Service_unavailable -> 503
+
| `Gateway_timeout -> 504
+
| `Http_version_not_supported -> 505
+
| `Variant_also_negotiates -> 506
+
| `Insufficient_storage -> 507
+
| `Loop_detected -> 508
+
| `Not_extended -> 510
+
| `Network_authentication_required -> 511
+
(* Custom code *)
+
| `Code c -> c
+
+
let of_int = function
+
(* Informational *)
+
| 100 -> `Continue
+
| 101 -> `Switching_protocols
+
| 102 -> `Processing
+
| 103 -> `Early_hints
+
(* Success *)
+
| 200 -> `OK
+
| 201 -> `Created
+
| 202 -> `Accepted
+
| 203 -> `Non_authoritative_information
+
| 204 -> `No_content
+
| 205 -> `Reset_content
+
| 206 -> `Partial_content
+
| 207 -> `Multi_status
+
| 208 -> `Already_reported
+
| 226 -> `Im_used
+
(* Redirection *)
+
| 300 -> `Multiple_choices
+
| 301 -> `Moved_permanently
+
| 302 -> `Found
+
| 303 -> `See_other
+
| 304 -> `Not_modified
+
| 305 -> `Use_proxy
+
| 307 -> `Temporary_redirect
+
| 308 -> `Permanent_redirect
+
(* Client Error *)
+
| 400 -> `Bad_request
+
| 401 -> `Unauthorized
+
| 402 -> `Payment_required
+
| 403 -> `Forbidden
+
| 404 -> `Not_found
+
| 405 -> `Method_not_allowed
+
| 406 -> `Not_acceptable
+
| 407 -> `Proxy_authentication_required
+
| 408 -> `Request_timeout
+
| 409 -> `Conflict
+
| 410 -> `Gone
+
| 411 -> `Length_required
+
| 412 -> `Precondition_failed
+
| 413 -> `Payload_too_large
+
| 414 -> `Uri_too_long
+
| 415 -> `Unsupported_media_type
+
| 416 -> `Range_not_satisfiable
+
| 417 -> `Expectation_failed
+
| 418 -> `I_m_a_teapot
+
| 421 -> `Misdirected_request
+
| 422 -> `Unprocessable_entity
+
| 423 -> `Locked
+
| 424 -> `Failed_dependency
+
| 425 -> `Too_early
+
| 426 -> `Upgrade_required
+
| 428 -> `Precondition_required
+
| 429 -> `Too_many_requests
+
| 431 -> `Request_header_fields_too_large
+
| 451 -> `Unavailable_for_legal_reasons
+
(* Server Error *)
+
| 500 -> `Internal_server_error
+
| 501 -> `Not_implemented
+
| 502 -> `Bad_gateway
+
| 503 -> `Service_unavailable
+
| 504 -> `Gateway_timeout
+
| 505 -> `Http_version_not_supported
+
| 506 -> `Variant_also_negotiates
+
| 507 -> `Insufficient_storage
+
| 508 -> `Loop_detected
+
| 510 -> `Not_extended
+
| 511 -> `Network_authentication_required
+
(* Unknown code *)
+
| c -> `Code c
+
+
let to_string t = string_of_int (to_int t)
+
+
let reason_phrase t =
+
match t with
+
(* Informational *)
+
| `Continue -> "Continue"
+
| `Switching_protocols -> "Switching Protocols"
+
| `Processing -> "Processing"
+
| `Early_hints -> "Early Hints"
+
(* Success *)
+
| `OK -> "OK"
+
| `Created -> "Created"
+
| `Accepted -> "Accepted"
+
| `Non_authoritative_information -> "Non-Authoritative Information"
+
| `No_content -> "No Content"
+
| `Reset_content -> "Reset Content"
+
| `Partial_content -> "Partial Content"
+
| `Multi_status -> "Multi-Status"
+
| `Already_reported -> "Already Reported"
+
| `Im_used -> "IM Used"
+
(* Redirection *)
+
| `Multiple_choices -> "Multiple Choices"
+
| `Moved_permanently -> "Moved Permanently"
+
| `Found -> "Found"
+
| `See_other -> "See Other"
+
| `Not_modified -> "Not Modified"
+
| `Use_proxy -> "Use Proxy"
+
| `Temporary_redirect -> "Temporary Redirect"
+
| `Permanent_redirect -> "Permanent Redirect"
+
(* Client Error *)
+
| `Bad_request -> "Bad Request"
+
| `Unauthorized -> "Unauthorized"
+
| `Payment_required -> "Payment Required"
+
| `Forbidden -> "Forbidden"
+
| `Not_found -> "Not Found"
+
| `Method_not_allowed -> "Method Not Allowed"
+
| `Not_acceptable -> "Not Acceptable"
+
| `Proxy_authentication_required -> "Proxy Authentication Required"
+
| `Request_timeout -> "Request Timeout"
+
| `Conflict -> "Conflict"
+
| `Gone -> "Gone"
+
| `Length_required -> "Length Required"
+
| `Precondition_failed -> "Precondition Failed"
+
| `Payload_too_large -> "Payload Too Large"
+
| `Uri_too_long -> "URI Too Long"
+
| `Unsupported_media_type -> "Unsupported Media Type"
+
| `Range_not_satisfiable -> "Range Not Satisfiable"
+
| `Expectation_failed -> "Expectation Failed"
+
| `I_m_a_teapot -> "I'm a teapot"
+
| `Misdirected_request -> "Misdirected Request"
+
| `Unprocessable_entity -> "Unprocessable Entity"
+
| `Locked -> "Locked"
+
| `Failed_dependency -> "Failed Dependency"
+
| `Too_early -> "Too Early"
+
| `Upgrade_required -> "Upgrade Required"
+
| `Precondition_required -> "Precondition Required"
+
| `Too_many_requests -> "Too Many Requests"
+
| `Request_header_fields_too_large -> "Request Header Fields Too Large"
+
| `Unavailable_for_legal_reasons -> "Unavailable For Legal Reasons"
+
(* Server Error *)
+
| `Internal_server_error -> "Internal Server Error"
+
| `Not_implemented -> "Not Implemented"
+
| `Bad_gateway -> "Bad Gateway"
+
| `Service_unavailable -> "Service Unavailable"
+
| `Gateway_timeout -> "Gateway Timeout"
+
| `Http_version_not_supported -> "HTTP Version Not Supported"
+
| `Variant_also_negotiates -> "Variant Also Negotiates"
+
| `Insufficient_storage -> "Insufficient Storage"
+
| `Loop_detected -> "Loop Detected"
+
| `Not_extended -> "Not Extended"
+
| `Network_authentication_required -> "Network Authentication Required"
+
(* Custom code - provide generic reason based on category *)
+
| `Code c ->
+
if c >= 100 && c < 200 then "Informational"
+
else if c >= 200 && c < 300 then "Success"
+
else if c >= 300 && c < 400 then "Redirection"
+
else if c >= 400 && c < 500 then "Client Error"
+
else if c >= 500 && c < 600 then "Server Error"
+
else "Unknown"
+
+
(* Classification functions *)
+
let is_informational t =
+
let code = to_int t in
+
code >= 100 && code < 200
+
+
let is_success t =
+
let code = to_int t in
+
code >= 200 && code < 300
+
+
let is_redirection t =
+
let code = to_int t in
+
code >= 300 && code < 400
+
+
let is_client_error t =
+
let code = to_int t in
+
code >= 400 && code < 500
+
+
let is_server_error t =
+
let code = to_int t in
+
code >= 500 && code < 600
+
+
let is_error t =
+
let code = to_int t in
+
code >= 400 && code < 600
+
+
(* Retry policy functions *)
+
let is_retryable t =
+
match t with
+
| `Request_timeout
+
| `Too_many_requests
+
| `Bad_gateway
+
| `Service_unavailable
+
| `Gateway_timeout -> true
+
| _ -> is_server_error t (* All 5xx errors are generally retryable *)
+
+
let should_retry_on_different_host t =
+
match t with
+
| `Bad_gateway
+
| `Service_unavailable
+
| `Gateway_timeout -> true
+
| _ -> false
+
+
(* Pretty printing *)
+
let pp ppf t =
+
Format.fprintf ppf "%d" (to_int t)
+
+
let pp_hum ppf t =
+
Format.fprintf ppf "%d %s" (to_int t) (reason_phrase t)
+161
stack/requests/lib/status.mli
···
+
(** HTTP status codes following RFC 7231 and extensions *)
+
+
(** {1 Status Categories} *)
+
+
type informational = [
+
| `Continue (** 100 - Client should continue with request *)
+
| `Switching_protocols (** 101 - Server is switching protocols *)
+
| `Processing (** 102 - Server has received and is processing the request *)
+
| `Early_hints (** 103 - Used to return some response headers before final HTTP message *)
+
]
+
(** 1xx Informational responses *)
+
+
type success = [
+
| `OK (** 200 - Standard response for successful HTTP requests *)
+
| `Created (** 201 - Request has been fulfilled; new resource created *)
+
| `Accepted (** 202 - Request accepted, processing pending *)
+
| `Non_authoritative_information (** 203 - Request processed, information may be from another source *)
+
| `No_content (** 204 - Request processed, no content returned *)
+
| `Reset_content (** 205 - Request processed, no content returned, reset document view *)
+
| `Partial_content (** 206 - Partial resource return due to request header *)
+
| `Multi_status (** 207 - XML, can contain multiple separate responses *)
+
| `Already_reported (** 208 - Results previously returned *)
+
| `Im_used (** 226 - Request fulfilled, response is instance-manipulations *)
+
]
+
(** 2xx Success responses *)
+
+
type redirection = [
+
| `Multiple_choices (** 300 - Multiple options for the resource delivered *)
+
| `Moved_permanently (** 301 - This and all future requests directed to the given URI *)
+
| `Found (** 302 - Temporary response to request found via alternative URI *)
+
| `See_other (** 303 - Response to request found via alternative URI *)
+
| `Not_modified (** 304 - Resource has not been modified since last requested *)
+
| `Use_proxy (** 305 - Content located elsewhere, retrieve from there (deprecated) *)
+
| `Temporary_redirect (** 307 - Connect again to different URI as provided *)
+
| `Permanent_redirect (** 308 - Connect again to a different URI using the same method *)
+
]
+
(** 3xx Redirection messages *)
+
+
type client_error = [
+
| `Bad_request (** 400 - Request cannot be fulfilled due to bad syntax *)
+
| `Unauthorized (** 401 - Authentication is possible but has failed *)
+
| `Payment_required (** 402 - Payment required, reserved for future use *)
+
| `Forbidden (** 403 - Server refuses to respond to request *)
+
| `Not_found (** 404 - Requested resource could not be found *)
+
| `Method_not_allowed (** 405 - Request method not supported by that resource *)
+
| `Not_acceptable (** 406 - Content not acceptable according to the Accept headers *)
+
| `Proxy_authentication_required (** 407 - Client must first authenticate itself with the proxy *)
+
| `Request_timeout (** 408 - Server timed out waiting for the request *)
+
| `Conflict (** 409 - Request could not be processed because of conflict *)
+
| `Gone (** 410 - Resource is no longer available and will not be available again *)
+
| `Length_required (** 411 - Request did not specify the length of its content *)
+
| `Precondition_failed (** 412 - Server does not meet request preconditions *)
+
| `Payload_too_large (** 413 - Request is larger than the server is willing or able to process *)
+
| `Uri_too_long (** 414 - URI provided was too long for the server to process *)
+
| `Unsupported_media_type (** 415 - Server does not support media type *)
+
| `Range_not_satisfiable (** 416 - Client has asked for unprovidable portion of the file *)
+
| `Expectation_failed (** 417 - Server cannot meet requirements of Expect request-header field *)
+
| `I_m_a_teapot (** 418 - I'm a teapot (RFC 2324) *)
+
| `Misdirected_request (** 421 - Request was directed at a server that is not able to produce a response *)
+
| `Unprocessable_entity (** 422 - Request unable to be followed due to semantic errors *)
+
| `Locked (** 423 - Resource that is being accessed is locked *)
+
| `Failed_dependency (** 424 - Request failed due to failure of a previous request *)
+
| `Too_early (** 425 - Server is unwilling to risk processing a request that might be replayed *)
+
| `Upgrade_required (** 426 - Client should switch to a different protocol *)
+
| `Precondition_required (** 428 - Origin server requires the request to be conditional *)
+
| `Too_many_requests (** 429 - User has sent too many requests in a given amount of time *)
+
| `Request_header_fields_too_large (** 431 - Server is unwilling to process the request *)
+
| `Unavailable_for_legal_reasons (** 451 - Resource unavailable for legal reasons *)
+
]
+
(** 4xx Client error responses *)
+
+
type server_error = [
+
| `Internal_server_error (** 500 - Generic error message *)
+
| `Not_implemented (** 501 - Server does not recognise method or lacks ability to fulfill *)
+
| `Bad_gateway (** 502 - Server received an invalid response from upstream server *)
+
| `Service_unavailable (** 503 - Server is currently unavailable *)
+
| `Gateway_timeout (** 504 - Gateway did not receive response from upstream server *)
+
| `Http_version_not_supported (** 505 - Server does not support the HTTP protocol version *)
+
| `Variant_also_negotiates (** 506 - Content negotiation for the request results in a circular reference *)
+
| `Insufficient_storage (** 507 - Server is unable to store the representation *)
+
| `Loop_detected (** 508 - Server detected an infinite loop while processing the request *)
+
| `Not_extended (** 510 - Further extensions to the request are required *)
+
| `Network_authentication_required (** 511 - Client needs to authenticate to gain network access *)
+
]
+
(** 5xx Server error responses *)
+
+
type standard = [
+
| informational
+
| success
+
| redirection
+
| client_error
+
| server_error
+
]
+
(** All standard HTTP status codes *)
+
+
type t = [
+
| `Code of int (** Any status code as an integer *)
+
| standard
+
]
+
(** HTTP status type *)
+
+
(** {1 Conversion Functions} *)
+
+
val to_int : t -> int
+
(** Convert status to its integer code *)
+
+
val of_int : int -> t
+
(** Convert an integer to a status *)
+
+
val to_string : t -> string
+
(** Get the string representation of a status code (e.g., "200", "404") *)
+
+
val reason_phrase : t -> string
+
(** Get the standard reason phrase for a status code (e.g., "OK", "Not Found") *)
+
+
(** {1 Classification Functions} *)
+
+
val is_informational : t -> bool
+
(** Check if status code is informational (1xx) *)
+
+
val is_success : t -> bool
+
(** Check if status code indicates success (2xx) *)
+
+
val is_redirection : t -> bool
+
(** Check if status code indicates redirection (3xx) *)
+
+
val is_client_error : t -> bool
+
(** Check if status code indicates client error (4xx) *)
+
+
val is_server_error : t -> bool
+
(** Check if status code indicates server error (5xx) *)
+
+
val is_error : t -> bool
+
(** Check if status code indicates any error (4xx or 5xx) *)
+
+
(** {1 Retry Policy} *)
+
+
val is_retryable : t -> bool
+
(** Check if a status code suggests the request could be retried.
+
Returns true for:
+
- 408 Request Timeout
+
- 429 Too Many Requests
+
- 502 Bad Gateway
+
- 503 Service Unavailable
+
- 504 Gateway Timeout
+
- Any 5xx errors *)
+
+
val should_retry_on_different_host : t -> bool
+
(** Check if a status code suggests retrying on a different host might help.
+
Returns true for:
+
- 502 Bad Gateway
+
- 503 Service Unavailable
+
- 504 Gateway Timeout *)
+
+
(** {1 Pretty Printing} *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty printer for status codes *)
+
+
val pp_hum : Format.formatter -> t -> unit
+
(** Human-readable pretty printer that includes both code and reason phrase *)
+11 -15
stack/requests/lib/stream.ml
···
let src = Logs.Src.create "requests.stream" ~doc:"HTTP Request Stream"
module Log = (val Logs.src_log src : Logs.LOG)
-
exception Timeout
-
exception TooManyRedirects of { url: string; count: int }
-
exception ConnectionError of string
-
exception HTTPError of { status: int; body: string; headers: Headers.t }
+
(* Import Error module exceptions *)
(* Helper to get client or use default *)
let get_client client =
···
| Some c -> c
| None -> failwith "No client provided"
-
(* Convert our Body.t to Cohttp body *)
-
let body_to_cohttp body = Body.to_cohttp_body body
+
(* Helper to get client or use default *)
(* Convert our Headers.t to Cohttp.Header.t *)
let headers_to_cohttp headers =
···
let cohttp_headers = headers_to_cohttp headers in
let cohttp_body = match body with
-
| Some b -> body_to_cohttp b
+
| Some b -> Body.to_cohttp_body b
| None -> None
in
···
let rec follow_redirect url redirects_left =
if redirects_left <= 0 then begin
Log.err (fun m -> m "Too many redirects (%d) for %s" max_redirects url);
-
raise (TooManyRedirects { url; count = max_redirects })
+
raise (Error.TooManyRedirects { url; count = max_redirects; max = max_redirects })
end else
(* Get location header from Cohttp headers *)
match Cohttp.Header.get cohttp_resp_headers "location" with
···
(* Convenience methods *)
let get ~sw ?client ?headers ?auth ?timeout ?follow_redirects ?max_redirects url =
request ~sw ?client ?headers ?auth ?timeout ?follow_redirects ?max_redirects
-
~method_:Method.GET url
+
~method_:`GET url
let post ~sw ?client ?headers ?body ?auth ?timeout url =
-
request ~sw ?client ?headers ?body ?auth ?timeout ~method_:Method.POST url
+
request ~sw ?client ?headers ?body ?auth ?timeout ~method_:`POST url
let put ~sw ?client ?headers ?body ?auth ?timeout url =
-
request ~sw ?client ?headers ?body ?auth ?timeout ~method_:Method.PUT url
+
request ~sw ?client ?headers ?body ?auth ?timeout ~method_:`PUT url
let delete ~sw ?client ?headers ?auth ?timeout url =
-
request ~sw ?client ?headers ?auth ?timeout ~method_:Method.DELETE url
+
request ~sw ?client ?headers ?auth ?timeout ~method_:`DELETE url
let head ~sw ?client ?headers ?auth ?timeout url =
-
request ~sw ?client ?headers ?auth ?timeout ~method_:Method.HEAD url
+
request ~sw ?client ?headers ?auth ?timeout ~method_:`HEAD url
let patch ~sw ?client ?headers ?body ?auth ?timeout url =
-
request ~sw ?client ?headers ?body ?auth ?timeout ~method_:Method.PATCH url
+
request ~sw ?client ?headers ?body ?auth ?timeout ~method_:`PATCH url
let upload ~sw ?client ?headers ?auth ?timeout ?method_ ?mime ?length
?on_progress ~source url =
-
let method_ = Option.value method_ ~default:Method.POST in
+
let method_ = Option.value method_ ~default:`POST in
let mime = Option.value mime ~default:Mime.octet_stream in
(* Wrap source with progress tracking if callback provided *)
+1 -5
stack/requests/lib/stream.mli
···
(** Streaming HTTP interface *)
-
(** Exceptions *)
-
exception Timeout
-
exception TooManyRedirects of { url: string; count: int }
-
exception ConnectionError of string
-
exception HTTPError of { status: int; body: string; headers: Headers.t }
+
(** Exceptions - re-exported from Error module for backward compatibility *)
val request :
sw:Eio.Switch.t ->
+19 -1
stack/requests/lib/timeout.ml
···
let connect t = t.connect
let read t = t.read
-
let total t = t.total
+
let total t = t.total
+
+
let pp ppf t =
+
let items = [] in
+
let items = match t.connect with
+
| Some c -> (Printf.sprintf "connect:%.1fs" c) :: items
+
| None -> items
+
in
+
let items = match t.read with
+
| Some r -> (Printf.sprintf "read:%.1fs" r) :: items
+
| None -> items
+
in
+
let items = match t.total with
+
| Some tot -> (Printf.sprintf "total:%.1fs" tot) :: items
+
| None -> items
+
in
+
match items with
+
| [] -> Format.fprintf ppf "no timeouts"
+
| _ -> Format.fprintf ppf "%s" (String.concat ", " (List.rev items))
+4 -1
stack/requests/lib/timeout.mli
···
(** Get read timeout *)
val total : t -> float option
-
(** Get total request timeout *)
+
(** Get total request timeout *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty printer for timeout configuration *)