(Janus was a two-faced Roman god who looked forward to the future and backwards to the past. His name gives us the name of the month January
)
A Clojure routing library. This is my second initiative on this front -the first as a collaborator on the wire
library. Inspiration has been taken from:
For something that web developers understand intuitively, the exact interpretation of "URL"s is rife with misunderstandings, competing standards and imcompatible interpretations. In an effort to be explicit, for janus a URI is the RFC 3986-compliant name by which a local resource is referenced on the web.
There are two primary requirements for a router:
format
strings.These are the core functions of a "pure" routing engine and they should be powerful and generally uncompromised by secondary concerns.
/
-separated and url-encoded segments (note that percent-encoding is not the same thing as RFC-compatible url-encoding, notably in the treatment of +
).See janus.route and janus.ring namespaces for usage notes.
The canonical data format for a route is a recursive tuple of two elements, the second of which is a three-tuple:
[identifiable [as-segment dispatchable routes]]
The outer sequence can be viewed as a pair, and is satisfied by a Clojure map entry. In fact, maps are the preferred way of representing a collection of mutually-exclusive routes:
{identifiable1 [as-segment1 dispatchable1 routes1]
identifiable2 [as-segment2 dispatchable2 routes2]
...
identifiableN [as-segmentN dispatchableN routesN]}
Only when the identification of route segments is not guaranteed to be unique does the order of routes become meaningful. At that point, routes can be represented by a sequence of pairs:
[[identifiable1 [as-segment1 dispatchable1 routes1]
[identifiable2 [as-segment2 dispatchable2 routes2]
...
[identifiableN [as-segmentN dispatchableN routesN]]
Routes are organized as a tree: there is one root route (which can most easily be represented as a vector tuple) and all child routes have exactly one parent.
The elements of a route are:
identifiable
is any instance of clojure.lang.Named
. This includes keywords and symbols. Keywords should be used when the matching of the route yields useful information. Symbols should be used for "constant" routes. This convention is leveraged in janus' ring support namespace.
as-segment
is anything satisfying janus.route.AsSegment
, which includes strings, keywords, regexes, functions and others. It is easy to extend AsSegment to accommodate custom interpretations. The role of as-segment
is twofold: to match inbound route segements (yielding route parameters) and to generate outbound route segements. The default semantics of each are as follows:
javal.lang.String
: Matches itself only, returning itself as a route parameter. Generates itself always.clojure.lang.Keyword
: Matches its name only, returning its name as a route parameter. Generates its name always.java.lang.Boolean
: Matches when true, never when false, and returns inbound segment. Generates its single string parameter (such as the inbound segment) as the outbound segment.java.util.regex.Pattern
: Matches when regex matches inbound segment and returns result of regex match as route parameter(s). Generates either the single result of matching inbound segment or the concatenation of a capture group-matched result.clojure.lang.Fn
: Matches when function applied to inbound segment returns a truthy result and returns the result as the route parameter. Generates that same result as the outbound route segment.clojure.lang.PersistentVector
: Matches when first element (an AsSegment) matches. Generates what second element (an AsSegment) generates. Note that the result of matching with the first element becomes the route parameter(s) that are provided as arguments to the second.dispatchable
is anything satisfying janus.route.Dispatchable
, which includes functions, vars and instances of clojure.lang.Named
. FIXME: Document better, why is protocol not enforced specifically during normalization?
routes
is a recursive seqable collection of child routes.
While the canonical format for routes is useful for understanding the full capabilities of routing in janus, there are many abbreviated representations that are acceptable. There is no run-time performance penalty for expressing routes in any abbreviated format as all routes are conformed to data types during router construction.
Only the identifiable
is required to represent a route. If the second element of the pair is missing or nil, default values are assigned based on the identifiable. For example:
[:root nil] => [:root [nil :root {}]]
[:root] => [:root [nil :root {}]]
Of course suppressing the second element is not possible when using a map to represent a sequence of routes.
If the second element of the route pair is missing components, they are either inferred from the identifier or default values are supplied.
Copyright © 2020 Chris Hapgood
Distributed under the Eclipse Public License version 1.0.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close