Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
andreypopp committed Apr 19, 2024
0 parents commit 1bf6f2d
Show file tree
Hide file tree
Showing 15 changed files with 882 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: main

on:
pull_request:
push:
schedule:
- cron: 0 1 * * MON

permissions: read-all

jobs:
build:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
ocaml-compiler:
- "4.14"
- "5.1"

runs-on: ${{ matrix.os }}

steps:
- name: checkout tree
uses: actions/checkout@v4

- name: set-up OCaml ${{ matrix.ocaml-compiler }}
uses: ocaml/setup-ocaml@v2
with:
ocaml-compiler: ${{ matrix.ocaml-compiler }}
opam-repositories: |
default: https://github.com/ocaml/opam-repository.git
- run: opam install . --deps-only --with-test

- run: opam exec -- dune build

- run: opam exec -- dune runtest
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_opam
_build
3 changes: 3 additions & 0 deletions .ocamlformat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
break-infix=fit-or-vertical
margin=74
parens-tuple=multi-line-only
116 changes: 116 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# `ppx_router`

A typed router for Dream.

## Usage

Install (custom opam repo is required as for now):
```
opam repo add andreypopp https://github.com/andreypopp/opam-repository.git
opam update
opam install ppx_router
```

Put this into your `dune` file:
```
(...
(preprocess (pps ppx_router))
```

Define your routes:
```ocaml
module Routes = struct
open Ppx_router_runtime.Types
type t =
| Home [@GET "/"]
| Hello of { name : string; repeat : int option } [@GET "/hello/:name"]
[@@deriving router]
end
```

Notice the `[@@deriving router]` annotation, which instruct to generate code
for routing based on the variant type definition.

Each branch in the variant type definition corresponds to a separate route, it
needs to have a `[@GET "/path"]` attribute (or `[@POST "/path"]`, etc.) which
specify a path pattern for the route.

The path pattern can contain named parameters, like `:name` in the example
above. In this case the parameter will be extracted from the path and used in
the route payload. All other fields from a route payload are considered query
parameters.

Now we can generate hrefs for these routes:
```ocaml
let () =
assert (Routes.href Home = "/");
assert (Routes.href (Hello {name="world"; repeat=1} = "/hello/world?repeat=1")
```

and define a handler for them:
```ocaml
let handle = Routes.handle (fun route _req ->
match route with
| Home -> Dream.html "Home page!"
| Hello {name; repeat} ->
let name =
match repeat with
| Some repeat ->
List.init repeat (fun _ -> name) |> String.concat ", "
| None -> name
in
Dream.html (Printf.sprintf "Hello, %s" name))
```

Finally we can use the handler in a Dream app:
```ocaml
let () = Dream.run ~interface:"0.0.0.0" ~port:8080 handle
```

## Custom path/query parameter types

When generating parameter encoding/decoding code for a parameter of type `T`,
`ppx_router` will emit the code that uses the following functions.

If `T` is a path parameter:
```ocaml
val T_of_url_path : string -> T option
val T_to_url_path : T -> string
```

If `T` is a query parameter:
```ocaml
val T_of_url_query : string list -> T option
val T_to_url_query : T -> string list
```

The default encoders/decoders are provided in `Ppx_router_runtime.Types` module
(this is why we need to `open` the module when defining routes).

To provide custom encoders/decoders for a custom type, we can define own
functions, for example:

```ocaml
module Modifier = struct
type t = Capitalize | Uppercase
let rec of_url_query : t Ppx_router_runtime.url_query_decoder = function
| [] -> None
| [ "capitalize" ] -> Some Capitalize
| [ "uppercase" ] -> Some Uppercase
| _ :: xs -> of_url_query xs (* let the last one win *)
let to_url_query : t Ppx_router_runtime.url_query_encoder = function
| Capitalize -> [ "capitalize" ]
| Uppercase -> [ "uppercase" ]
end
```

After that one can use `Modifier.t` in route definitions:

```ocaml
type t =
| Hello of { name : string; modifier : Modifier.t } [@GET "/hello/:name"]
[@@deriving router]
```
22 changes: 22 additions & 0 deletions dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(library
(name ppx_router)
(modules ppx_router)
(public_name ppx_router)
(libraries uri ppxlib containers)
(kind ppx_rewriter)
(ppx_runtime_libraries ppx_router.runtime)
(preprocess
(pps ppxlib.metaquot)))

(library
(name ppx_router_runtime)
(modules ppx_router_runtime)
(public_name ppx_router.runtime)
(libraries containers dream routes)
(preprocess
(pps ppxlib.metaquot)))

(executable
(name ppx_router_test)
(modules ppx_router_test)
(libraries ppx_router))
24 changes: 24 additions & 0 deletions dune-project
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(lang dune 3.11)

(generate_opam_files true)

(source
(github andreypopp/ppx_router))

(authors "Andrey Popp")

(maintainers "Andrey Popp")

(license LICENSE)

(package
(name ppx_router)
(depends
(ocaml
(>= 4.14))
dune
ppxlib
containers
routes
dream
uri))
Loading

0 comments on commit 1bf6f2d

Please sign in to comment.