Raven Path Generators and Route Enumeration

Raven, the URL dispatching library on which Vinland is based, has been updated to add a couple new features, in addition to general cleanup, additional docstrings, and the addition of declaim statements.

New features:

  1. path generators
  2. list-routes utility

Path generators

A lambda function is now compiled for each route symbol passed to COMPILE-ROUTER. Any dynamic path parameters are passed to the function as keyword arguments in order to return a path string. Macro ROUTE-PATH can be used to lookup and call the path generator function for the specified route name.

As an example, suppose the following Raven router has been compiled:

(defparameter *router* (foo.lisp.raven:compile-router
                         `(("/widgets" ,'widgets)
                           ("/widgets/:widget-id" ,'widget))))

(defparameter *web* (funcall *router* :clack))

Now, from your routes, you can call ROUTE-PATH to return a path string. Package FOO.LISP.VINLAND/WEB re-exports ROUTE-PATH from Raven, and also defines macro ROUTE-URL, which returns the same string, except prefixed by the origin.

Example:

(foo.lisp.raven:route-path 'widgets)
;; => "/widgets"

(foo.lisp.raven:route-path 'widget :|widget-id| "xyz")
;; => "/widgets/xyz"

Vinland example:

(foo.lisp.vinland/web:route-path 'widget :|widget-id| "xyz")
;; => "/widgets/xyz"

;; NOTE: *ORIGIN* is set by default in Vinland controllers.
(let ((foo.lisp.vinland:*origin* "http://acme.example.com"))
  (foo.lisp.vinland/web:route-url 'widget :|widget-id| "xyz"))
;; => "http://acme.example.com/widgets/xyz"

The call to COMPILE-ROUTER to return a router function happens after loading route handlers and other code, so to make the path generators accessible from controllers and other files loaded before router compilation, the path-generators are stored in a hash-table that is dynamically bound on dispatch.

If say, you want to call ROUTE-PATH from dao.lisp or another file loaded prior to the controllers, you are free to do so: the hash-table containing the path generators stores each entry with a keyword key, coerced from the route-name symbol. This means that route-name symbols must now be unique between packages, so the following will no longer compile:

(defparameter *router* (foo.lisp.raven:compile-router
                         `(("/about" ,'about)
                           ("/foo/about" ,'foo:about))))

List-routes

The router function returned from COMPILE-ROUTER can now list routes matching a specified prefix, returning either a list or printing to standard-out.

(defparameter *router* (foo.lisp.raven:compile-router
                         `(("/" ,'root)
                           ("/widgets" ,'widgets)
                           ("/widgets/:widget-id" ,'widget))))

The following returns a list of ROUTE-INFO structs for all routes:

(funcall *router* '(:list-routes "/"))

To return only the widgets routes, run:

(funcall *router* '(:list-routes "/widgets"))

To print to *STANDARD-OUTPUT* without returning a list (slightly better formatted), you can call:

(funcall *router* '(:print-routes "/widgets"))

NOTE: you can also find a specific matching route; this isn’t a new feature but worth pointing out here:

(funcall *router* '(:find-route "/widgets/xyz"))

The CLI files in the Vinland skeleton have been updated, so that a list-routes subcommand can now be called from the generated binary (created with (asdf:make :demo-app)):

The following example prints all routes to standard-out:

./bin/demo-app list-routes /