Module Res.Named

Human readable URLs for identifying resources.

This module provides generic support for handling a /name/id[/path] URL scheme to identify resources.

The stable id identifier is preceeded by a human readable name that can change over time without breaking the resource resolution. The name segment is simply ignored for resolving the resource and redirected to the right one if it doesn't match the name of the ressource identified by id at the time of resolution.

Let id and name be the identifier and name of a given ressource. The URL response scheme should be:

  1. /name/id[/path] is determined by the resource specific logic.
  2. /name'/id[/path] with name' not an identifier and different from name is a 301 moved permanently to /name/id[/path].
  3. /name is either a 404 not found or, if the corresponding id is easy to find, a 301 moved permanently to /name/id.
  4. /id[/path] is a 301 moved permanently to /name/id[/path].
  5. /s0/s1[/path] with both s0 and s1 not parsing as identifiers can be used freely without interfering with the scheme.

Note that for this to work the name and id syntax must be distinguishable unambiguously.

The Named.resolve function handles point 2 and 4 in a generic manner. The Named.name_of_string function provides a (non-injective) naming scheme for arbitrary strings that is human readable, compatible with URL path segment syntax and unambiguously distinguishable from non-negative numerical identifers (e.g. Res.Id).


val resolve : ?eq:('name -> 'name -> bool) -> get_res:('id -> ('res, Webs.Http.Response.t) Stdlib.result) -> res_name:('res -> 'name) -> res_url:('name -> 'id -> string) -> req_name:'name option -> req_id:'id -> unit -> ('res, Webs.Http.Response.t) Stdlib.result

resolve ~eq ~get_res ~res_name ~res_url ~req_name ~req_id () implements the 301 redirection logic spelled out in the preamble of this module.

Given a request's parsed identifier req_id and name req_name (if any):

  1. If get_res req_id is Error _ this is returned by the function.
  2. If get_res req_id is Ok res, let n be res_name res.
  3. If req_name is None a 301 moved permanently Error _ to res_url n req_id is returned.
  4. If req_name is Some n' and eq n n' is false a 301 moved permanently Error _ to res_url n req_id is returned.
  5. Otherwise, Ok res is returned.

eq defaults to Stdlib.(=). res_url must return a value suitable for the Webs.Http.Headers.location header.


type name = string

The type for names.

val name_of_string : string -> name

name_of_string s maps s to a name that can be used as a human readable URL path segment. It transforms s to a sequence of '-' separated tokens. A '-' is appended to the empty string and to sequences of US-ASCII digits only so that they can be distinguished from numerical identifiers (e.g. Res.Id). More precisely this:

  1. Replaces all US-ASCII characters except letters and digits by '-'.
  2. Lowercases upper US-ASCII letters.
  3. Suppresses initial and final '-'s and compresses consecutive '-'.
  4. Appends "-" if the result is empty or only has US-ASCII digits.

The function is not injective. It is idempotent and preserves all non US-ASCII UTF-8 characters. It is meant to be applied before percent encoding.