Page
Library
Module
Module type
Parameter
Class
Class type
Source
Dream_htmlSourceTyped, extensible HTML form decoder with error reporting for form field validation failures. Powerful chained decoding functionality–the validation of one field can depend on the values of other decoded fields.
val form :
'a Form.t ->
?csrf:bool ->
Dream.request ->
[> 'a Dream.form_result | `Invalid of (string * string) list ] Dream.promiseType-safe wrapper for Dream.form. Similarly to that, you can match on the result:
type new_user = { name : string; email : string }
let new_user =
let open Dream_html.Form in
let+ name = required string "name"
and+ email = required string "email" in
{ name; email }
(* POST /users *)
let post_users req =
match%lwt Dream_html.form new_user req with
| `Ok { name; email } -> (* ... *)
| `Invalid errors -> Dream.json ~code:422 ( (* ...render errors... *) )
| _ -> Dream.empty `Bad_RequestType-safe wrapper for Dream.all_queries. Can be used to decode the query parameters into a typed value.
include module type of Pure_htmlThese are the types of the final values which get rendered.
E.g. id="toast".
Either a tag, a comment, or text data in the markup.
Same as to_string but render void tags as XML-style self-closing tags.
Same as pp but render void tags as XML-style self-closing tags.
Special handling for string-value attributes so they can use format strings i.e. string interpolation.
A 'void element': https://developer.mozilla.org/en-US/docs/Glossary/Void_element with no children.
Tags which can have attributes but can contain only text. The text can be formatted.
attr name is a new attribute which does not carry any payload. E.g.
let required = attr "required"string_attr name fmt is a new string-valued attribute which allows formatting i.e. string interpolation of the value. Note, the fmt argument is required due to the value restriction.
Convenience for attributes whose values should be URIs. Takes care of both URI-encoding and attribute escaping, as recommended in https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#common-mistake.
Examples
a [href "/blog?tags=iamsafe\"></a><script>alert('Pwned')</script>"] [txt "Tags: tag1 | tag2"]
==>
<a href="/blog?tags=iamsafe%22%3E%3C/a%3E%3Cscript%3Ealert('Pwned')%3C/script%3E">Tags: tag1 | tag2</a>
a [href "/foo?a=1&b=2 3&c=4<5&d=6>5"] [txt "Test"]
==>
<a href="/foo?a=1&b=2%203&c=4%3C5&d=6%3E5">Test</a>Build a tag which can contain only a URI. The URI is escaped with the same rules as a uri_attr.
A text node inside the DOM e.g. the 'hi' in <b>hi</b>. Allows string interpolation using the same formatting features as Printf.sprintf:
b [] [txt "Hello, %s!" name]Or without interpolation:
b [] [txt "Bold of you."]HTML-escapes the text value. You can use the ~raw param to bypass escaping:
let user_input = "<script>alert('I like HTML injection')</script>" in
txt ~raw:true "%s" user_inputA comment that will be embedded in the rendered HTML, i.e. <!-- comment -->. The text is HTML-escaped.
concat node list is the list of nodes joined together into a single node, with each element separated by node.
Add an attribute to a tag.
let toast msg = p [id "toast"] [txt "%s" msg]
let toast_oob = toast "ok." +@ Hx.swap_oob "true"Get the value of an existing attribute.
let toast = p [id "toast"] [txt "OK."]
let toast_id = toast.@["id"]Get whether a node is null (empty) or not. Useful for conditional rendering of UIs when you are passed in a node and you don't know if it's empty or not.
All standard HTML attributes and tags. Some attributes and tags have the same name, e.g. style. To disambiguate them, attributes have a _ (underscore) suffix.
val respond :
?status:[< Dream.status ] ->
?code:int ->
?headers:(string * string) list ->
node ->
Dream.response Dream.promiseval send :
?text_or_binary:[< Dream.text_or_binary ] ->
?end_of_message:[< Dream.end_of_message ] ->
Dream.websocket ->
node ->
unit Dream.promiseType-safe wrapper for Dream.send.
Type-safe wrapper for Dream.set_body. Sets the body to the given node and sets the Content-Type header to text/html.
Type-safe wrapper for Dream.write.
Convenience to add a CSRF token generated by Dream into your form. Type-safe equivalent of Dream.csrf_tag.
form
[action "/foo"]
[csrf_tag req; input [name "bar"]; input [type_ "submit"]]Bidirectional paths with type-safe path segment parsing and printing using OCaml's built-in format strings, and fully plug-and-play compatible with Dream routes.
A path that can be used for routing and can also be printed as an attribute value.
Wrapper for a Dream route that represents the ability to parse path parameters and pass them to the handler function with the correct types.
val path :
('r, unit, Dream.response Dream.promise) format ->
('p, unit, string, attr) format4 ->
('r, 'p) pathpath request_fmt attr_fmt is a router path. The dream-html.ppx provides a more convenient way.
Without PPX: let order = path "/orders/%s" "/orders/%s"
With PPX: let%path order = "/orders/%s"
Refer to the PPX documentation for instructions on using it.
⚠️ Due to the way Dream's router works, all parameter captures happen between / characters and the end of the path (or the ? character, whichever comes first). Eg, /foo/%s/bar/%d is valid, but /foo/%s.%s (note the dot character) is not a valid capture.
⚠️ If a route is matched but the data type does not match, a 400 Bad Request response will be returned. The following type conversion specs are supported:
%s capture a string and pass it to the handler
%*s capture the rest of the path and pass the captured length and string to the handler
%c capture a char
%d or %i capture an int
%x capture a hexadecimal int
%X capture an uppercase hexadecimal int
%o capture an octal int
%ld capture an int32
%Ld capture an int64
%f capture a float
%B capture a bool
⚠️ We are actually using Dream's built-in router, not our own, and Dream's router doesn't distinguish between parameter types. So, to Dream both /%s and /%d are the same path. It will route the request to whichever happens to be first in the route list, and that one will succeed or fail depending on its type and the request target.
path_attr attr path is an HTML attribute with the path parameters filled in from the given values. Eg,
let%path order = "/orders/%s"
open Dream_html
open HTML
a [path_attr href order "yzxyzc"] [txt "My Order"]Renders: <a href="/orders/yzxyzc">My Order</a>
Use this instead of hard-coding your route URLs throughout your app, to make it easy to refactor routes with minimal effort.
pp_path is a pretty-printer for path values. For a path like path "/foo" "/foo", it will print out /foo.
Type-safe wrappers for Dream.get and so on. Using the PPX, eg:
let%path order = "/orders/%s"
let get_order = get order (fun request order_id ->
...
a [path_attr href order order_id] [txt "Your order"]
...
)use middlewares routes is a route that is composed of all the given routes with the middlewares attached to them.
static_asset path is a route that handles serving the static file at the path. Importantly, it sets an immutable cache header which remains valid for a year.
⚠️ Be sure that the resource has a unique identifier because it will be cached immutably. The dreamwork CLI tool automates this for you. See Dreamwork.
TL;DR:
dreamwork setupstatic/assets/ subdirectory with filesdune buildStatic.routes to the app's main Dream routerimg [path_attr src Static.Assets.icon_png] which will render with a revision hash based on the file contents, calculated at build timedreamwork is a CLI tool that helps set up and manage static file paths and routes with proper content-based version hashes. The static files will live inside a dune component called static and in the static/assets subdirectory. Suppose you have the following directory tree:
static / dune assets / css / app.css js / app.jsThe dune file defines a library component that will make the following module available:
module Static : sig
val routes : Dream.route
(** This route will serve all of the following paths. *)
module Assets : sig
module Css : sig
val app_css : (Dream.response Dream.promise, attr) path
end
module Js : sig
val app_js : (Dream.response Dream.promise, attr) path
end
end
endSo, you can just stick Static.routes in your router and it will correctly serve the files from the filesystem with an immutable cache of 1 year; and you can use Static.Assets.Css.app_css and so on in your dream-html markup code and it will correctly render with a ?rev=... query parameter that uniquely identifies this revision of the file with a content-based hash for cache-busting purposes:
link [rel "stylesheet"; path_attr href Static.Assets.Css.app_css
(*
<link
rel="stylesheet"
href="/static/assets/css/app.css?rev=17fb8161afc85df86156ea1f3744c8a2"
>
*) script [path_attr src Static.Assets.Js.app_js] ""
(*
<script src="/static/assets/js/app.js?rev=677645e5ac37d683c5039a85c41c339f">
</script>
*)You control the directory subtree under assets; the dreamwork CLI just helps you define the dune component that generates the above module structure. The module structure mirrors the directory tree structure.
The entry point to dreamwork is the dreamwork setup command, which creates static/, assets/, and dune. In the dune file it defines a code generation rule which uses the dreamwork static command to generate the OCaml code.
So, you just need to run dreamwork setup to initialize the directory structure and code generation. After that, you can add and remove any files inside assets/ as you want and on the next dune build the Static module structure will be updated accordingly.
Live reload script injection and handling. Adapted from Dream.livereload middleware. This version is not a middleware so it's not as plug-and-play as that, but on the other hand it's much simpler to implement because it uses type-safe dream-html nodes rather than parsing and printing raw HTML. See below for the 3-step process to use it.