Macros and functions for creating, combining, and composing routes for lmug apps
WARNING: This project is in high-flux and is awaiting further developments in the lmug library.
Inspired by Clojure's Compojure and based on the original LFE-YAWS routing work done in the lfest project, lrootes improves upon its predecessor by focusing on route combination utilizing the same principles as the Compojure project, principally by integrating the work of lmug.
lrootes accomplishes this by ensuring that routes are simply functions which
return iolists
of data structures. The data strcutures represent an HTTP-verb+URL dispatch.
This allows lrootes routes to be composed (since they are functions) and keeps
the inner workings simple (because it's just iolist
s of tuples).
This project assumes that you have Erlang and rebars, and [lfetool]()
Just add it to your rebar.config
deps:
{deps, [
...
{lrootes, ".*", {git, "git@github.com:lfe-mug/lrootes.git", "master"}}
]}.
Then:
$ rebar3 compile
This shows bare minimum usage:
(include-lib "lrootes/include/routing.lfe")
(defroutes webapp
('GET "/hello/world" (rootes-http:html-ok "Hello, World"))
('NOTFOUND
(rootes-http:html-not-found "Page Not Found")))
(defapp (webapp))
This shows a simple combination of routes:
(include-lib "lrootes/include/routing.lfe")
(defroutes webapp
('GET "/hello/world" (rootes-http:html-ok "Hello, World"))
('NOTFOUND
(rootes-http:html-not-found "Page Not Found")))
(defroutes api
('GET "/api/get-status" (rootes-http:html-ok "All systems go.")))
(makeapp
(list (webapp)
(api)))
There are several ways in which one may choose to combine routes for an app; the above example shows the list constructor approach; the literal data approach is an obvious alternative:
(makeapp `(,(webapp)
,(api)))
Another option open to developers is to compose the routes:
(makeapp (api (webapp)))
Or, if you're familiar with Clojure and enjoy using the LFE clj library, you can use one of the threshing macros:
(makeapp (-> (webapp)
(api)))
(include-lib "lrootes/include/routing.lfe")
(defroutes order-api
;; top-level
('GET "/"
(lfest-html-resp:ok "Welcome to the Volvo Store!"))
;; single order operations
('POST "/order"
(create-order (lfest:get-data arg-data)))
('GET "/order/:id"
(get-order id))
('PUT "/order/:id"
(update-order id (lfest:get-data arg-data)))
('DELETE "/order/:id"
(delete-order id))
;; order collection operations
('GET "/orders"
(get-orders))
;; payment operations
('GET "/payment/order/:id"
(get-payment-status id))
('PUT "/payment/order/:id"
(make-payment id (lfest:get-data arg-data)))
;; error conditions
('ALLOWONLY
('GET 'POST 'PUT 'DELETE)
(lfest-json-resp:method-not-allowed))
('NOTFOUND
(lfest-json-resp:not-found "Bad path: invalid operation.")))
(makeapp (order-api))
With LFE releases 1.10.x and higher, you can define multiple modules in a
single file, thus allowing you to provide multiple appmods
in a single
module. We can take advantage of this to succinctly define appmods
that
are protected and those that are publicly accessible.
TBD
TBD
A few important things to note here:
arg-data
passed from
YAWS; this contains all the data you could conceivably need to process a
request. (You may need to import the yaws_api.hrl
in your module to
parse the data of your choice, though.)(defroutes ...)
macro; the variable will then be
accessible from the function you provide in that route.(defroutes ...)
macro generates the routes/3
function; it's
three arguments are the HTTP verb (method name), the path info (a list of
path segments, with the ":varname"
segments converted to varname
/
variable segments), and then the arg-data
variable from YAWS.Notes for new library [this will be converted to content once implementation is done]:
(defroutes ...)
- deines a named functioniolist
of the routes defined by the function.iolist
of routes(makeapp ...)
macro must be called in the
module that is specified in the YAWS configuration with an appmods
directive(makeapp ...)
macro defines the out/1
function which YAWS
requires application modules to providelfest needs to provide YAWS with an out/1
function. The location of this
function is configured in your etc/yaws.conf
file in the
<appmods ...>
directives (it can be repeated for supporting multiple
endpoints).
YAWS will call this function with one argument: the YAWS arg
record
data. Since this function is the entry point for applications running under
YAWS, it is responsible for determining how to process all requests.
The out/1
function in lfest-based apps calls the routes/3
function
generated by the (defroutes ...)
mamcro.
The route definition macro does some pretty heavy remixing of the routes
defined in (defroutes ...)
. The route definition given in the "Usage"
section above actually expands to the following LFE before being compiled to
a .beam
:
#((define-function routes
(match-lambda
(('GET () arg-data)
(call 'lfest-html-resp 'ok "Welcome to the Volvo Store!"))
(('POST ("order") arg-data)
(create-order (call 'lfest 'get-data arg-data)))
(('GET ("order" id) arg-data) (get-order id))
(('PUT ("order" id) arg-data)
(update-order id (call 'lfest 'get-data arg-data)))
(('DELETE ("order" id) arg-data) (delete-order id))
(('GET ("orders") arg-data) (get-orders))
(('GET ("payment" "order" id) arg-data) (get-payment-status id))
(('PUT ("payment" "order" id) arg-data)
(make-payment id (call 'lfest 'get-data arg-data)))
((method p a)
(when
(not
(if (call 'erlang '=:= 'GET method)
'true
(if (call 'erlang '=:= 'POST method)
'true
(if (call 'erlang '=:= 'PUT method)
'true
(call 'erlang '=:= 'DELETE method))))))
(call 'lfest-json-resp 'method-not-allowed))
((method path arg-data)
(call 'lfest-json-resp 'not-found "Bad path: invalid operation."))))
6)
When it is compiled, the routes/3
function is available for use from
wherever you have defined your routes.