Module Url.Parser

Parse URLs.

Basics

type url = t
type ('a, 'b) t

The URL parser type

Path

val string : (string -> 'a, 'a) t

string will parse a segment of the path as string.

The segment will be percent-decoded automatically.

input

parse result

"http://host/alice/"

Some "alice"

"http://host/bob"

Some "bob"

"http://host/%D0%91%D0%BE%D0%B1"

Some "Боб"

"http://host/42/"

Some "42"

"http://host/"

None

val int : (int -> 'a, 'a) t

int will parse a segment of the path as integer.

input

parse result

"http://host/alice/"

None

"http://host/bob"

None

"http://host/42/"

Some "42"

"http://host/"

None

val s : string -> ('a, 'a) t

s str will parse a segment of the path if it matches the given string str.

The segment will be percent-decoded automatically.

For example, the parser s "blog" </> int will behave as follows:

input

parse result

"http://host/blog/42"

Some 42

"http://host/tree/42"

None

val custom : (string -> 'a option) -> ('a -> 'b, 'b) t

custom f will parse a segment of the path by applying the function f to the raw segment.

Example:

    let positive_int: (int -> 'a, 'a) Parser.t =
        Parser.custom @@
            fun s ->
                match int_of_string_opt s with
                | Some i when i > 0 ->
                    Some i
                | _ ->
                    None

The example parser produces the following results:

input

parse result

"http://host/0"

None

"http://host/-42"

None

"http://host/42"

Some 42

val (</>) : ('a, 'b) t -> ('b, 'c) t -> ('a, 'c) t

p1 </> p2 combines the segment parsers p1 and p2 and returns a new parser which will parse two path segments.

Example:

    let blog: (int -> 'a, 'a) Parser.t =
        let open Parser in
        s "blog" </> int

The example parser will behave like this:

input

parse result

"http://host/blog"

None

"http://host/blog/42"

Some 42

"http://host/blog/42/"

Some 42

val map : 'a -> ('a, 'b) t -> ('b -> 'c, 'c) t

map f parser transforms the parser via f.

f can be a function taking as many values as the parser produces (see example 1) or, in case the parser produces no values at all, f can be a variant constructor or a variant tag (see example 2).

Examples 1:

    type date = {year: int; month: int; day: int}

    let date_parser: (date -> 'a, 'a) Parser.t =
        let open Parser in
        map
            (fun year month day -> {year; month; day})
            (int </> int </> int)

The parser in example 1 will produce the following results:

input

parse result

"http://host/2025/08/"

None

"http://host/2025/08/07"

Some {year = 2025; month = 8; day = 7}

Example 2:

    let language = Haskell | Ocaml | Rust

    let language_parser: (language -> 'a, 'a) Parser.t =
        let open Parser in
        one_of
            [
                map Haskell (s "hs");
                map OCaml (s "ml");
                map Rust (s "rs");
            ]

The parser in example 2 will produce the following results:

input

parse result

"http://host/hs"

Some Haskell

"http://host/ml"

Some OCaml

"http://host/rs"

Some Rust

"http://host/py"

None

val one_of : ('a, 'b) t list -> ('a, 'b) t

one_of parsers runs the given parsers in the order they are provided. The result is the result of the first succeeding parser or None if all of them fail.

Example:

    type route =
        | Index
        | Article of int
        | Comment of {id: int; article_id: int}

    let route_parser: (route -> 'a, 'a) Parser.t =
        let open Parser in
        one_of
            [
                map Index top;
                map (fun id -> Article id) (s "blog" </> int);
                map
                    (fun article_id id -> Comment {id; article_id})
                    (s "blog" </> int </> s "comment" </> int)
            ]

The example parser will behave like this:

input

parse result

"http://host/"

Some Index

"http://host/blog"

None

"http://host/blog/42"

Some (Article 42)

"http://host/blog/42"

Some (Article 42)

"http://host/blog/42/comment"

None

"http://host/blog/42/comment/5"

Some (Comment {id = 5; article_id = 42}))

val top : ('a, 'a) t

top creates a parser that does not consume any path segment.

It can be used together with one_of in order to use a common prefix for multiple parsers:

    type route = Overview | Post of int

    let blog: (route -> 'a, 'a) Parser.t =
        let open Parser in
        s "blog" </>
            one_of
                [
                    map Overview top;
                    map (fun id -> Post id) (s "post" </> int);
                ]

The example parser produces the following results:

input

parse result

"http://host/"

None

"http://host/blog"

Some Overview

"http://host/post/42"

None

"http://host/blog/post/42"

Some (Post 42)

Query

module Query : sig ... end

Parse query parameters

val (<?>) : ('a, 'b -> 'c) t -> 'b Query.t -> ('a, 'c) t

url_parser </?> query_parser combines a url_parser with a query_parser.

For example, the parser s "blog" <?> Query.string "search" produces the following results:

input

parse result

"http://host/blog"

Some None

"http://host?search=ocaml"

Some (Some "ocaml")

"http://host?search="

Some (Some "")

"http://host?search"

Some (None)

val query : 'a Query.t -> ('a -> 'b, 'b) t

query query_parser converts query_parser to a URL parser.

This is useful if a URL has an empty path and we want to parse query parameters.

Example:

    (* The following parsers are equivalent *)

    let search_term_parser1: (string -> 'a, 'a) Parser.t =
        let open Parser in
        query (Query.string "search")

    let search_term_parser2: (string -> 'a, 'a) Parser.t =
        let open Parser in
        top <?> Query.string "search"

The example parsers behave as follows:

input

parse result

"http://host"

Some None

"http://host?search=ocaml"

Some (Some "ocaml")

"http://host?search="

Some (Some "")

"http://host?search"

Some (None)

Fragment

val fragment : (string option -> 'a) -> ('a -> 'b, 'b) t

fragment f creates a fragment parser that produces a value by calling f on the fragment part of the URL.

The fragment part is percent-decoded automatically.

For example. the parser s "excercises" </> fragment Fun.id produces the folloing results:

input

parse result

"http://host/excercises"

Some None

"http://host/excercises#"

Some (Some "")

"http://host/excercises#6"

Some (Some "6")

Run parsers

val parse : ('a -> 'a, 'a) t -> url -> 'a option

parse parser url runs the given parser on the given url.

Example:

    type route = Home | Blog of int | Not_found

    let route_parser: (route -> 'a, 'a) Parser.t =
        let open Parser in
        one_of
            [
                map Home top;
                map (fun id -> Blog id) (s "blog" </> int);
            ]

    let route_of_string (str: string): route =
        match of_string str with
        | None ->
            Not_found
        | Some url ->
            Option.value ~default:Not_found (parse route_parser url)

The route_of_string function above produces the following results:

input

parse result

"/blog/42"

Not_found

"https://example.com/"

Home

"https://example.com/blog"

Not_found

"https://example.com/blog/42"

Blog 42

"https://example.com/blog/42/"

Blog 42

"https://example.com/blog/42#introduction"

Blog 42

"https://example.com/blog/42?search=ocaml"

Blog 42

"https://example.com/settings"

Not_found