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
6let src = Logs.Src.create "requests.method" ~doc:"HTTP Methods"
7
8module Log = (val Logs.src_log src : Logs.LOG)
9
10type t =
11 [ `GET
12 | `POST
13 | `PUT
14 | `DELETE
15 | `HEAD
16 | `OPTIONS
17 | `PATCH
18 | `CONNECT
19 | `TRACE
20 | `Other of string ]
21
22let to_string = function
23 | `GET -> "GET"
24 | `POST -> "POST"
25 | `PUT -> "PUT"
26 | `DELETE -> "DELETE"
27 | `HEAD -> "HEAD"
28 | `OPTIONS -> "OPTIONS"
29 | `PATCH -> "PATCH"
30 | `CONNECT -> "CONNECT"
31 | `TRACE -> "TRACE"
32 | `Other s -> String.uppercase_ascii s
33
34let of_string s =
35 match String.uppercase_ascii s with
36 | "GET" -> `GET
37 | "POST" -> `POST
38 | "PUT" -> `PUT
39 | "DELETE" -> `DELETE
40 | "HEAD" -> `HEAD
41 | "OPTIONS" -> `OPTIONS
42 | "PATCH" -> `PATCH
43 | "CONNECT" -> `CONNECT
44 | "TRACE" -> `TRACE
45 | other -> `Other other
46
47let pp ppf m = Fmt.pf ppf "%s" (to_string m)
48
49let is_safe = function
50 | `GET | `HEAD | `OPTIONS | `TRACE -> true
51 | `POST | `PUT | `DELETE | `PATCH | `CONNECT | `Other _ -> false
52
53let is_idempotent = function
54 | `GET | `HEAD | `PUT | `DELETE | `OPTIONS | `TRACE -> true
55 | `POST | `PATCH | `CONNECT | `Other _ -> false
56
57type body_semantics = Body_required | Body_optional | Body_forbidden
58
59let request_body_semantics = function
60 | `POST | `PUT | `PATCH -> Body_required
61 | `DELETE | `OPTIONS -> Body_optional
62 | `GET ->
63 Body_optional
64 (* RFC 9110 Section 9.3.1: GET body has no defined semantics *)
65 | `HEAD ->
66 Body_forbidden
67 (* RFC 9110 Section 9.3.2: identical to GET but no body in response *)
68 | `TRACE -> Body_forbidden (* RFC 9110 Section 9.3.8: MUST NOT send body *)
69 | `CONNECT ->
70 Body_forbidden (* RFC 9110 Section 9.3.6: no body in CONNECT request *)
71 | `Other _ -> Body_optional (* Unknown methods - allow body for flexibility *)
72
73let has_request_body = function
74 | `POST | `PUT | `PATCH -> true
75 | `GET | `HEAD | `DELETE | `OPTIONS | `CONNECT | `TRACE -> false
76 | `Other _ -> false (* Conservative default for unknown methods *)
77
78let is_cacheable = function
79 | `GET | `HEAD -> true
80 | `POST -> true (* POST can be cacheable with explicit headers *)
81 | `PUT | `DELETE | `PATCH | `OPTIONS | `CONNECT | `TRACE | `Other _ -> false
82
83let equal m1 m2 =
84 match (m1, m2) with
85 | `Other s1, `Other s2 ->
86 String.equal (String.uppercase_ascii s1) (String.uppercase_ascii s2)
87 | m1, m2 -> m1 = m2
88
89let compare m1 m2 =
90 match (m1, m2) with
91 | `Other s1, `Other s2 ->
92 String.compare (String.uppercase_ascii s1) (String.uppercase_ascii s2)
93 | m1, m2 -> Stdlib.compare m1 m2