Module Webs_bazaar.Kurl

Kinds of URL requests and their formatters.

This module provides support to describe and handle URL requests by multiple client-defined OCaml types in a bidirectional and modular manner.

A kind value represents a subset of bare URL requests by values of a custom request OCaml type. Kinds are then used:

TODO.

Bare URL requests

type bare

The type for bare URL requests. This embodies an HTTP method, an URL path, an URL query and an optional URL path file extension used by some URL formatting modes.

val bare : ?ext:string -> ?query:Webs.Http.Query.t -> Webs.Http.Method.t -> Webs.Http.Path.t -> bare

bare m p ~query ~ext is an URL request to path p with method m, query query (defaults to Webs.Http.Query.empty) and URL path file extension ext (defaults to "").

module Bare : sig ... end

Bare URL requests.

Encoders

type 'a enc = 'a -> bare

The type for encoding an URL request value of type 'a to a bare URL request.

Decoders

type 'a dec = bare -> ('a option, Webs.Http.Response.t) Stdlib.result

The type for decoding a bare URL request to a request value of type 'a. The path in bare url is relative to the service tree binding point.

The function returns:

  • Ok (Some r), if the request belongs to the kind of URL requests with value r.
  • Ok None, if the request does not belong to the kind of URL requests. This lets other services attached to tree consider the prefix or an extension of it.
  • Error r, if the request was meant to belong to the kind of URL requests but directly responding with r at that point was deemed appropriate. Examples are bad request, not allowed, etc. Possibly not found but if you want to let other subtrees consider the prefix use Ok None

TODO.

  • We could generalize over the error. But then we need to carry it over everywhere.

Decoder helpers

TODO. Provide easy redirect.

val allow : 'a Webs.Http.Method.constraint' list -> bare -> ('a, Webs.Http.Response.t) Stdlib.result

meths ms u is:

  • Ok (Bare.meth u) if List.mem (Bare.meth u, Bare.meth u) ms
  • Error _ with a 405 not allowed response otherwise.
val ok : 'a -> ('a option, 'e) Stdlib.result

ok v is Ok (Some v).

val no_match : ('a option, 'e) Stdlib.result

no_match is Ok None.

Kinds

type 'a kind

The type for kinds of URL request of type 'a.

val kind : ?root_is:[ `Dir of string option | `File ] -> ?name:string -> 'a enc -> 'a dec -> 'a kind

kind ?root_is ?name enc dec is a kind using enc and dec to codec bare URL request values. name is a name used for debugging purposes (defaults to "").

root_is defines formatting behaviour of the root path ([""]) relative to the service binding path. With:

  • `File the binding path encodes the root. Note that since the binding path must be well-formed it is guaranteed to lack a trailing slash. If extension formatting is requested and there is one in the encoded bare, it is appended to it. For example if the URL kind is bound to path ["mykind"] and enc encodes a bare URL with path [""] the formatted URL will be "/mykind".
  • `Dir seg the binding path with a trailing slash is the resulting path. If extension formatting is requested and seg is Some seg then both segment seg (e.g. "index") and the extension are appended, otherwise the path is left as is. For example if the URL kind is bound to path ["mykind"] and enc encodes a bare URL with path [""] the formatted URL will be "/mykind/" or /mykind/seg if extension formatting is requested.

TODO. Should root_is also affect decoding ?

IMPORTANT. Kinds have a notion of identity and they can only be bound once in a tree.

module Kind : sig ... end

Kinds of URL requests

val any : bare kind

any is Kind.bare ~name:"any" ~root_is:(`Dir (Some "index")) (). This is a catch-all bare request kind defined. It is always added at the root of URL formatters.

In conjunction with Bare.of_req_referer it allows for example to easily format relative URLs for requests performed by HTML page components or for request that do not exist in the service space like 404.

You can also bind it at the root of your service tree as the last service. It will provide you with a catch all handler.

Kinded URL requests

type t =
  1. | V : 'a kind * 'a -> t

The type for an URL request of a given kind.

val v : 'a kind -> 'a -> t

v k u is V (k, u).

Services

type 'a service

The type for services handling URL requests with a value of type 'a, see service.

val service : 'b kind -> ('b -> 'a) -> 'a service

service k f is a service handling URL requests of kind k with f.

The return type of 'a is common to all services. Usually this is a function which when given arguments common to all services effectively handles the request. The service type simply hides the first, request kind specific, argument of these functions.

val map_service : ('a -> 'b) -> 'a service -> 'b service

map_service f s applies f to the service of s.

Service trees

type 'a tree

The type for service trees binding constant HTTP paths to services of type 'a.

val empty : unit -> 'a tree

empty () is an empty service tree.

Request handling

val find_service : 'a tree -> bare -> ('a option, Webs.Http.Response.t) Stdlib.result

find_service t u finds the service for handling u's Webs.Http.Request.path. Ok None is returned if no URL request kind matched. Error _ is returned if a URL request kind matched but errored.

Binding services

val bind : Webs.Http.Path.t -> 'a service -> 'a tree -> 'a tree

bind p s t is t with well-formed path p bound to s. Note shorter bindings to p in t take precedence, but s takes precedence over existing bindings at p. More precisely:

  • If there is a service s' on a prefix of p in t, s will only be invoked for those requests for which the kind of s' decodes to Ok None.
  • If there is a service s' at p in t, s' will only be invoked for those requests for which the kind of s decodes Ok None.

Raises Invalid_argument if s is already bound in t or if p is not well-formed.

val unbind_service : 'a service -> 'a tree -> 'a tree

unbind s t is t with the binding of s removed (if any).

val unbind_path : Webs.Http.Path.t -> 'a tree -> 'a tree

unbind_path p t is t with all services bound to p removed (if any).

val service_path : 'a service -> 'a tree -> Webs.Http.Path.t option

service_path s t is the path to which s is bound in t (if any).

val path_services : Webs.Http.Path.t -> 'a tree -> 'a service list

path_services p t are the services bound to p in t (if any).

val fold_paths : (Webs.Http.Path.t -> 'a service list -> 'b -> 'b) -> 'a tree -> 'b -> 'b

fold_paths f t acc folds f and acc over all paths of t in lexicographic order. This includes paths on which the service list is empty.

URL request formatters

type fmt

The type for URL request formatters.

module Fmt : sig ... end

URL request formatters.

Path shenanigans

HTTP path handling is a mess.

Well-formed paths

For this module a well-formed path is either the root path [""] or a non-empty Webs.Http.Request.path devoid of empty segments (including trailing ones).

To lessen the mess, in Kurl you can only bind to well-formed paths in service trees and URL formatters. Note that this doesn't mean that your kinds can't produce non well-formed paths.

It's not mandatory but also a good idea to use Webs.Http.Request.clean_path before handing your requests to find_service.

Root paths

HTTP clients makes no difference between:

https://example.org = https://example.org/

but they do between:

https://example.org/a ≠ https://example.org/a/

This leads to all sorts of difficulties for abitrarily rebinding services, since the root is treated differently.

For this reason in tree services we never give an empty path to decode. That is if a service is bound on /a/b both requests with path /a/b/ and /a/b will result in path / ("root" of the service) in the bare URL to decode. For this reason it's a good idea to canonicalize requests before with Webs.Http.Request.clean_path.

Todo describe Kind.root_is.