Drup / furl

Formatted url
MIT License
49 stars 3 forks source link
format gadt ocaml uri

Formatted Url

This library allows to create type-safe formatted urls in the spirit of the format type from the standard library. It is backed by re and uri

This is work in progress, if you have any remarks or proposition, please open an issue. The API will be changed until I'm happy with it. Contributions are welcome. Also, it may still be buggy.

To use this library, you can pin it:

opam pin add furl https://github.com/Drup/furl.git

Quick look

Let's imagine we want to build a REST service indexing Camelidaes.

let camlidae () = Furl.host "www.camlidae.ml"

We can query a Camelidae by name:

let by_name () =
  Furl.(~$camlidae / "name" /% String /? nil)
val by_name : unit -> (string -> 'a, 'a) Furl.furl

Notice how the type of the value describes the contained parameters.

Let's consider the type ('f,'r) Furl.furl. 'f is the type of a function corresponding to the parameters of the url. 'r is the return type, which could be anything at this point.

We can also query a list of camelidae by humps:

let by_humps () =
  Furl.(~$camlidae / "humps" /% Int /? nil)
val by_humps : unit -> (int -> 'a, 'a) Furl.furl

This is nice, but we want to refine the list by humps to only show non extinct camelidaes:

let by_humps () =
  Furl.(~$camlidae / "humps" /% Int /? ("extinct",Opt Bool) ** nil)
val by_humps : unit -> (int -> bool option -> 'a, 'a) Furl.furl

We can now build a handler answering to these endpoints:

let handle_camlidaes =
  Furl.match_url [
    Furl.(route ~$by_name) (fun n ->
      List.filter (fun c -> c.name = n) list_camlidaes
    ) ;
    Furl.(route ~$by_humps) (fun humps -> function
      | None ->
        List.filter (fun c -> c.humps = humps) list_camlidaes
      | Some b ->
        List.filter (fun c -> c.humps = humps && c.extinct = b) list_camlidaes
    );
  ]
    ~default:(fun _uri -> failwith "This is not a camlidae.")
val handle_camlidaes : Uri.t -> camlidae list

You can then give this handler to your favorite web server and Tada, a camelidae web API.

You can also expose the formatted urls for clients:

let query_by_humps = Furl.eval @@ by_humps ()
val query_by_humps : int -> bool option -> Uri.t

Then you can use your favorite http client to get the answer we all want:

fetch_http @@ query_by_hump 2 (Some false) ;;
["Bactrian camel"; "Wild camel"]

See camlidae.ml for the complete working example.

Principles

furl uses GADT in a similar manner than format in the standard library, but specialized for urls and regexps. The matching against multiple uris is done with re.

urls are separated into Furl.url which are not yet finalized and Furl.furl which are finalized. This separation is done to make Furl.url a pure datastructure that can be serialized, moved around (and sent to the client in the context of eliom). Furl.furl contains an url and the list of converters.