lfe-mug / lrootes

Macros and functions for creating, combining, and composing routes for lmug apps
3 stars 0 forks source link

lrootes Build Status

lrootes project logo

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.

Table of Contents

Introduction

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 iolists of tuples).

Dependencies

This project assumes that you have Erlang and rebars, and [lfetool]()

Installation

Just add it to your rebar.config deps:


{deps, [
    ...
    {lrootes, ".*", {git, "git@github.com:lfe-mug/lrootes.git", "master"}}
  ]}.

Then:

$ rebar3 compile

Usage

Simple Example

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))

Combination Example

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)))

REST Service Example

(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))

YAWS Auth Example

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

Fine-grained Access Example

TBD

Concepts

How It Works

A few important things to note here:

Notes for new library [this will be converted to content once implementation is done]:

Behind the Scenes

lfest 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.