diff --git a/cohttp-eio/src/header.ml b/cohttp-eio/src/header.ml index 25bf9c01a..0bf6aea5d 100644 --- a/cohttp-eio/src/header.ml +++ b/cohttp-eio/src/header.ml @@ -1,5 +1,4 @@ type name = string (* Header name, e.g. Date, Content-Length etc *) -type value = string (* Header value, eg 10, text/html, chunked etc *) type lname = string let canonical_name nm = @@ -14,11 +13,10 @@ type 'a header = .. type 'a header += | Content_length : int header | Transfer_encoding : [ `chunked | `compress | `deflate | `gzip ] list header - | H : lname -> value header + | H : lname -> string header -type 'a decoder = value -> 'a -type 'a encoder = 'a -> value -type 'a undecoded = 'a Lazy.t +type 'a encoder = 'a -> string +type 'a decoder = string -> 'a type (_, _) eq = Eq : ('a, 'a) eq class codec = @@ -98,7 +96,12 @@ class codec = let name (c : #codec) = c#name -type v = V : 'a header * 'a Lazy.t -> v +type 'a value = 'a Lazy.t + +let value = Fun.id +let decode : type a. a value -> a = fun v -> Lazy.force v + +type v = V : 'a header * 'a value -> v class virtual t = object @@ -130,9 +133,9 @@ let make code = make_n code [] let of_name_values (c : #codec) l = List.map - (fun (name, value) -> + (fun (name, v) -> let h = c#header (lname name) in - let v = lazy (c#decoder h value) in + let v = value (lazy (c#decoder h v)) in V (h, v)) l |> make_n c @@ -140,6 +143,7 @@ let of_name_values (c : #codec) l = let length (t : #t) = List.length t#to_list let add_lazy (type a) (t : t) (h : a header) v = + let v = value v in t#modify (fun l -> V (h, v) :: l) let add (type a) (t : t) (h : a header) v = add_lazy t h (lazy v) @@ -154,9 +158,8 @@ let add_name_value (t : t) ~name ~value = add_lazy t h v let encode : type a. t -> a header -> a -> string = fun t h v -> t#encoder h v -let decode : type a. a undecoded -> a = Lazy.force -let exists (t : #t) (f : < f : 'a. 'a header -> 'a undecoded -> bool >) = +let exists (t : #t) (f : < f : 'a. 'a header -> 'a value -> bool >) = let rec aux = function | [] -> false | V (h, v) :: tl -> if f#f h v then true else aux tl @@ -168,7 +171,7 @@ let find_opt (type a) (t : #t) (h : a header) = | [] -> None | V (h', v) :: tl -> ( match t#equal h h' with - | Some Eq -> ( try Some (Lazy.force v :> a) with _ -> None) + | Some Eq -> ( try Some (decode v :> a) with _ -> None) | None -> aux tl) in aux t#to_list @@ -177,27 +180,25 @@ let find (type a) (t : #t) (h : a header) = let rec aux = function | [] -> raise Not_found | V (h', v) :: tl -> ( - match t#equal h h' with - | Some Eq -> (Lazy.force v :> a) - | None -> aux tl) + match t#equal h h' with Some Eq -> (decode v :> a) | None -> aux tl) in aux t#to_list -let find_all (type a) (t : #t) (h : a header) : a undecoded list = +let find_all (type a) (t : #t) (h : a header) : a value list = let[@tail_mod_cons] rec aux = function | [] -> [] | V (h', v) :: tl -> ( match t#equal h h' with - | Some Eq -> (v :> a undecoded) :: aux tl + | Some Eq -> (v : a value) :: aux tl | None -> aux tl) in aux t#to_list -let update (t : #t) (f : < f : 'a. 'a header -> 'a undecoded -> 'a option >) = +let update (t : #t) (f : < f : 'a. 'a header -> 'a value -> 'a value option >) = t#modify (List.filter_map (fun (V (h, v)) -> let v = f#f h v in - Option.map (fun v -> V (h, lazy v)) v)) + Option.map (fun v -> V (h, v)) v)) let remove (type a) ?(all = false) (t : #t) (h : a header) = t#modify (fun headers -> @@ -213,13 +214,12 @@ let remove (type a) ?(all = false) (t : #t) (h : a header) = in headers) -type binding = B : 'a header * 'a undecoded -> binding +type binding = B : 'a header * 'a value -> binding -let iter (t : #t) (f : < f : 'a. 'a header -> 'a undecoded -> unit >) = +let iter (t : #t) (f : < f : 'a. 'a header -> 'a value -> unit >) = List.iter (fun (V (h, v)) -> f#f h v) t#to_list -let fold_left (t : #t) (f : < f : 'a. 'a header -> 'a undecoded -> 'b -> 'b >) - acc = +let fold_left (t : #t) (f : < f : 'a. 'a header -> 'a value -> 'b -> 'b >) acc = List.fold_left (fun acc (V (h, v)) -> f#f h v acc) acc t#to_list let to_seq (t : #t) = @@ -229,6 +229,6 @@ let to_name_values (t : #t) = List.map (fun (V (h, v)) -> let name = t#name h in - let value = encode t h (Lazy.force v) in + let value = encode t h (decode v) in (name, value)) t#to_list diff --git a/cohttp-eio/src/header.mli b/cohttp-eio/src/header.mli index d1fca515a..196a2c03c 100644 --- a/cohttp-eio/src/header.mli +++ b/cohttp-eio/src/header.mli @@ -15,9 +15,6 @@ type lname = private string [Content-Type -> content-type], [Date -> date], [Transfer-Encoding -> transfer-encoding] etc. See {!val:lname}. *) -type value = string -(** [value] is an untyped HTTP header value, eg 10, text/html, chunked etc *) - val canonical_name : string -> name (** [canonical_name s] converts [s] to a canonical header name value. See {!type:name}. *) @@ -50,25 +47,21 @@ type 'a header = .. type 'a header += | Content_length : int header | Transfer_encoding : [ `chunked | `compress | `deflate | `gzip ] list header - | H : lname -> value header (** A generic header. *) - -(** {1 Codec - Header Encoder & Decoder} *) + | H : lname -> string header (** A generic header. *) -type 'a decoder = value -> 'a -(** [decoder] converts {!type:value} to type ['a]. To denote an error while - decoding, an OCaml exception value is raised. *) +(** {1 Encoder/Decoder} *) -type 'a encoder = 'a -> value +type 'a encoder = 'a -> string (** [encoder] converts a typed value ['a] to its string representation. *) -type 'a undecoded -(** ['a undecoded] represents a lazy value that is as yet undecoded. See - {!val:decode}. *) +type 'a decoder = string -> 'a +(** [decoder] converts {!type:value} to type ['a]. To denote an error while + decoding, an OCaml exception value is raised. *) (** [eq] is OCaml GADT equality. *) type (_, _) eq = Eq : ('a, 'a) eq -(** {2 codec} +(** {1 codec} [codec] defines encoders, decoders and equality for the following HTTP headers: @@ -80,8 +73,8 @@ type (_, _) eq = Eq : ('a, 'a) eq Users looking to combine both custom headers and headers defined in this module are recommended to inherit this class. - {i Example} Here we define two custom headers [Header1] and [Header2] and - implement codec for it in object [custom_codec]. + Here we define two custom headers [Header1] and [Header2] and implement + codec for it in object [custom_codec]. {[ type 'a Header.header += @@ -157,13 +150,37 @@ class codec : val name : #codec -> 'a header -> name (** [name codec h] is the canonical name for header [h]. *) -(** {1 Collection of Headers} *) +(** {1 Headers} *) type t = private < codec ; .. > -(** [t] represents a collection of HTTP headers. {b Note} [t] is concurrency - safe. *) +(** [t] represents a collection of HTTP headers. + + Accessing - find/add/remove/udpate [t] is concurrency safe. Howerver, note + decoding a value is not concurrency-safe. + + See {!val:decode}. *) + +type 'a value +(** ['a value] represents a HTTP header value that is lazily created. + + See {!val:decode}. *) + +val value : 'a Lazy.t -> 'a value +(** [value lazy_val] creates a {!type:value} value. + + {[ + Header.value (lazy (int_of_string "19")) + ]} *) -(** {2 Create} *) +val decode : 'a value -> 'a +(** [decode v] decodes [v]. + + Note: [Header.decode] is not concurrency-safe. Consider using locks + {!module:Eio.Mutex} or {!module:Stdlib.Mutex}. + + @raise exn if decoding results in an error. *) + +(** {1 Create} *) val make : #codec -> t (** [make codec] is an empty [t]. *) @@ -177,7 +194,7 @@ val of_name_values : #codec -> (string * string) list -> t val length : t -> int (** [length t] is total count of headers in [t]. *) -(** {2 Add} *) +(** {1 Add} *) val add_lazy : t -> 'a header -> 'a Lazy.t -> unit (** [add_lazy t h lazy_v] adds header [h] and its corresponding typed lazy value @@ -186,32 +203,28 @@ val add_lazy : t -> 'a header -> 'a Lazy.t -> unit val add : t -> 'a header -> 'a -> unit (** [add t h v] add header [h] and its corresponding typed value [v] to [t].*) -val add_value : t -> 'a header -> value -> unit +val add_value : t -> 'a header -> string -> unit (** [add_value t h s] adds header [h] and its corresponding untyped, undecoded string value to [t].*) -val add_name_value : t -> name:lname -> value:value -> unit +val add_name_value : t -> name:lname -> value:string -> unit (** [add_name_value t ~name ~value] lazily (i.e. undecoded) add header with [name] and [value] to [t]. *) -(** {2 Encode, Decode} *) +(** {1 Encode} *) -val encode : t -> 'a header -> 'a -> value +val encode : t -> 'a header -> 'a -> string (** [encode codec h v] encodes the value of header [h]. The encoder is used as defined in [codec]. *) -val decode : 'a undecoded -> 'a -(** [decode codec v] decodes [v]. - - @raise exn if decoding results in an error. *) - -(** {2 Find} *) +(** {1 Find} *) -val exists : t -> < f : 'a. 'a header -> 'a undecoded -> bool > -> bool +val exists : t -> < f : 'a. 'a header -> 'a value -> bool > -> bool (** [exists t f] iterates over [t] and applies [f#f h v] where [h] and [v] are respectively header and undecoded value as it exists in [t]. It returns - [true] if any of the items in [t] returns [true] for [f#f h v]. See - {!val:decode} to decode [v]. *) + [true] if any of the items in [t] returns [true] for [f#f h v]. + + See {!val:decode} to decode [v]. *) val find_opt : t -> 'a header -> 'a option (** [find_opt t h] is [Some v] if [h] exists in [t]. It is [None] if [h] doesn't @@ -223,18 +236,21 @@ val find : t -> 'a header -> 'a @raise Not_found if [h] is not found in [t]. @raise exn if decoding [h] results in an error. *) -val find_all : t -> 'a header -> 'a undecoded list +val find_all : t -> 'a header -> 'a value list (** [find_all t h] is a list of undecoded values [v] corresponding to header - [h]. It is an empty list if [h] doesn't exist in [t]. See {!val:decode} to - decode [v]. *) + [h]. It is an empty list if [h] doesn't exist in [t]. + + See {!val:decode} to decode [v]. *) -(** {2 Update, Remove} *) +(** {1 Update, Remove} *) -val update : t -> < f : 'a. 'a header -> 'a undecoded -> 'a option > -> unit -(** [update t f] iterates over [t] and applies [f#f h v] where [h] and [v] are - respectively header and undecoded value as it exists in [t]. If +val update : t -> < f : 'a. 'a header -> 'a value -> 'a value option > -> unit +(** [update t f] iterates over [t] and applies [f#f h v] to each element. [h] + and [v] are respectively header and undecoded value as it exists in [t]. If [f#f h v = Some v'] then the value of [h] is updated to [v']. If [None] then - [h] is removed from [t]. See {!val:decode} to decode [v]. *) + [h] is removed from [t]. + + See {!val:decode} to decode [v]. *) val remove : ?all:bool -> t -> 'a header -> unit (** [remove t h] removes the last added header [h] from [t]. @@ -243,27 +259,31 @@ val remove : ?all:bool -> t -> 'a header -> unit if [true] then all headers equal to [h] are removed from [t]. Default value is [false]. *) -(** {2 Iter, Fold, Seq} *) +(** {1 Iter, Fold, Seq} *) (** [binding] represents a typed header and its corresponding undecoded value. - See {!type:undecoded} and {!val:decode}. *) -type binding = B : 'a header * 'a undecoded -> binding -val iter : t -> < f : 'a. 'a header -> 'a undecoded -> unit > -> unit + See {!type:value} and {!val:decode}. *) +type binding = B : 'a header * 'a value -> binding + +val iter : t -> < f : 'a. 'a header -> 'a value -> unit > -> unit (** [iter t f] iterates over [t] and applies [f#f h v] where [h] and [v] are - respectively header and undecoded value as it exists in [t]. See - {!val:decode} to decode [v]. *) + respectively header and undecoded value as it exists in [t]. -val fold_left : - t -> < f : 'a. 'a header -> 'a undecoded -> 'b -> 'b > -> 'b -> 'b + See {!val:decode} to decode [v]. *) + +val fold_left : t -> < f : 'a. 'a header -> 'a value -> 'b -> 'b > -> 'b -> 'b (** [fold_left t f acc] folds over [t] and applies [f#f h v acc] where [h] and - [v] are respectively header and undecoded value as it exists in [t]. See - {!val:decode} to decode [v]. *) + [v] are respectively header and undecoded value as it exists in [t]. + + See {!val:decode} to decode [v]. *) val to_seq : t -> binding Seq.t -(** [to_seq t] returns a sequence of {!type:binding}s. *) +(** [to_seq t] returns a sequence of {!type:binding}s. + + See {!val:decode} to decode [v]. *) -val to_name_values : t -> (name * value) list +val to_name_values : t -> (name * string) list (** [to_name_values t] a list of [(name,value)] tuple. @raise exn if decoding any of the values results in an error. *) diff --git a/cohttp-eio/tests/header.md b/cohttp-eio/tests/header.md index 6219ca53b..824f9ef30 100644 --- a/cohttp-eio/tests/header.md +++ b/cohttp-eio/tests/header.md @@ -179,14 +179,14 @@ val t3 : Header.t = ```ocaml # let f = object - method f: type a. a Header.header -> a Header.undecoded -> bool = + method f: type a. a Header.header -> a Header.value -> bool = fun t v -> let v = Header.decode v in match t, v with | Header.Content_length, 200 -> true | _ -> false end ;; -val f : < f : 'a. 'a Header.header -> 'a Header.undecoded -> bool > = +val f : < f : 'a. 'a Header.header -> 'a Header.value -> bool > = # Header.exists t f ;; - : bool = true @@ -245,16 +245,17 @@ Apply `update`. ```ocaml # let f = object - method f: type a. a Header.header -> a Header.undecoded -> a option = + method f: type a. a Header.header -> a Header.value -> a Header.value option = fun h v -> - let v = Header.decode v in - match h, v with - | Header.Content_length, 200 -> Some 2000 - | Header.H nm, "20" when ((nm :> string) = "age") -> Some "40" + let v' = Header.decode v in + match h, v' with + | Header.Content_length, 200 -> Some (Header.value (lazy 2000)) + | Header.H nm, "20" when ((nm :> string) = "age") -> Some (Header.value (lazy "40")) | Header.H nm, "blah2" when ((nm :> string) = "blah2") -> None | _ -> Some v end;; -val f : < f : 'a. 'a Header.header -> 'a Header.undecoded -> 'a option > = +val f : + < f : 'a. 'a Header.header -> 'a Header.value -> 'a Header.value option > = # Header.update t f ;; @@ -288,7 +289,7 @@ val f : < f : 'a. 'a Header.header -> 'a Header.undecoded -> 'a option > = - : unit = () # Header.(find_all t (H blah)) ;; -- : string Header.undecoded list = [] +- : string Header.value list = [] # Header.length t ;; - : int = 4 @@ -300,14 +301,14 @@ val f : < f : 'a. 'a Header.header -> 'a Header.undecoded -> 'a option > = ```ocaml # let f = object - method f: type a. a Header.header -> a Header.undecoded -> unit = + method f: type a. a Header.header -> a Header.value -> unit = fun h v -> let v = Header.decode v in let value = Header.encode t h v in let name = (Header.name t h :> string) in Printf.printf "\n%s: %s" name value end;; -val f : < f : 'a. 'a Header.header -> 'a Header.undecoded -> unit > = +val f : < f : 'a. 'a Header.header -> 'a Header.value -> unit > = # Header.iter t f ;; Content-Type: text/html @@ -323,7 +324,7 @@ We get a list of headers in string form using `fold_left`. ```ocaml # let f = object - method f: type a. a Header.header -> a Header.undecoded -> 'b -> 'b = + method f: type a. a Header.header -> a Header.value -> 'b -> 'b = fun h v acc -> let v = Header.decode v in match h with @@ -334,8 +335,7 @@ We get a list of headers in string form using `fold_left`. val f : < f : 'a. 'a Header.header -> - 'a Header.undecoded -> - (string * string) list -> (string * string) list > = + 'a Header.value -> (string * string) list -> (string * string) list > = # Header.fold_left t f [];;