Giter VIP home page Giter VIP logo

bidi's Introduction

bidi

"bidi bidi bidi" -- Twiki, in probably every episode of Buck Rogers in the 25th Century

In the grand tradition of Clojure libraries we begin with an irrelevant quote.

Bi-directional URI dispatch. Like Compojure, but when you want to go both ways. For example, many routing libraries can route a URI to a request handler, but only some of these (for example, Pedestal, gudu) can take a reference to a handler, plus some environment, and generate a URI. If you are serving REST resources, you should be providing links to other resources, and without full support for generating URIs from handlers your code will become coupled with your routing. In short, hard-coded URIs will eventually break.

In bidi, routes are data structures, there are no macros here. Generally speaking, data structures are to be preferred over code structures. When routes are defined in a data structure there are numerous advantages - they can be read in from a configuration file, generated, computed, transformed by functions and introspected - all things which macro-based DSLs make harder. This project also avoids 'terse' forms for the route definitions, it is better to learn and live with a single data structure.

The logic for matching routes is separated from the responsibility for handling requests. This is an important architectural principle. So you can match on things that aren't necessarily handlers, like keywords which you can use to lookup your handlers, or whatever you want to do. Separation of concerns and all that.

Installation

Add the following dependency to your project.clj file

[bidi "1.3.0"]

Take 5 minutes to learn bidi (using the REPL)

Let's create a route that matches /index.html. Routes are pairs, the first element is always a pattern.

user> (def route ["/index.html" :index])

Let's try to match that route to a path.

user> (use 'bidi.bidi)
nil
user> (match-route "/index.html" route)
{:handler :index}

We have a match! A map is returned with a single entry with a :handler key and :index as the value. We could use this result, for example, to look up a Ring handler in a map mapping keywords to Ring handlers.

What happens if we try a different path?

user> (match-route "/another.html" route)
nil

We get a nil. Nil means 'no route matched'.

Now, let's go in the other direction.

user> (path-for :index route)
"/index.html"

We ask bidi to use the same route definition to tell us the path that would match the :index hander. In this case, it tells us index.html. So if you were generating a link to this handler from another page, you could use this function in your view logic to create the link instead of hardcoding in the view template (This gives your code more resiliance to changes in the organisation of routes during development).

Mutliple routes

Let's pretend we have some articles in our blog and each article URI matches the pattern /articles/:id/index.html where :id is the unique article number. Rather than including 'special' characters in strings, we construct the pattern in segments using a simple Clojure vector: ["/articles/" :id "index.html"]. We combine this route with our existing :index route inside another vector.

    user> (def routes ["/" [
            ["index.html" :index]
            [["articles/" :id "/article.html"] :article]
           ]])
    #'user/routes

Now we can match on an article path.

user> (match-route "/articles/123/article.html" routes)
{:handler :article, :params {:id "123"}}
user> (match-route "/articles/999/article.html" routes)
{:handler :article, :params {:id "999"}}

The result is a map as before. This time it includes a :params entry with a map of route parameters.

To generate the path we need to supply the value of :id as extra arguments to the path-for function.

user> (path-for :article routes :id 123)
"/articles/123/article.html"
user> (path-for :article routes :id 999)
"/articles/999/article.html"

Apart from a few extra bells and whistles documented in the rest of this README, that's basically it.

Wrapping as a Ring handler

You can use functions (or symbols that represent them) as pattern targets. If you choose to do this a benefit it that you can wrap your routes to form a Ring handler (similar to what Compojure's routes and defroutes does).

(require '[bidi.bidi :refer (make-handler)])

(def handler
  (make-handler ["/blog"
                 [["/index.html" blog-index]
                  [["/article/" :id ".html"] blog-article-handler]
                  [["/archive/" :id "/old.html"] (fn [req] {:status 404}]]]))

Guards

Method guards

By default, routes don't dispatch on the request method and behave like Compojure's ANY routes. That's fine if your handlers deal with the request methods themselves, as Liberator's do. However, you can specify a method by wrapping a route (or routes) in a pair, where the first element is a keyword denoting the methods.

["/"
 [["blog"
   [[:get [["/index" (fn [req] {:status 200 :body "Index"})]]]]]]]

Other guards

You can also restrict routes by other criteria. In this example, the /zip route is only matched if the server name in the request is juxt.pro. Guards are specified by maps. Map entries can specify a single value, a set of possible values or even a predicate to test a value.

["/"
 [["blog"
   [[:get [["/index" (fn [req] {:status 200 :body "Index"})]]]
    [{:request-method :post :server-name "juxt.pro"}
     [["/zip" (fn [req] {:status 201 :body "Created"})]]]]
   ]]]

Route definitions

This BNF grammar describes the structure of the routes definition data structures.

RoutePair ::= [Pattern Matched]

Pattern ::= Path | [ PatternSegment+ ] | MethodGuard | GeneralGuard

MethodGuard ::= :get :post :put :delete :head :options

GeneralGuard ::= [ GuardKey GuardValue ]* (a map)

GuardKey ::= Keyword

GuardValue ::= Value | Set | Function

Path ::= String

PatternSegment ::= String | Regex | Keyword | [ (String | Regex) Keyword ]

Matched ::= Function | Symbol | Keyword | RoutePair | [ RoutePair+ ]

Composeability

Route structures are composeable. They are consistent and easy to generate. A future version of bidi may contain macros to reduce the number of parenthesis needed to create route structures by hand.

Extensibility

The implementation is based on Clojure protocols which allows the route syntax to be extended outside of this library.

License

Copyright © 2013, JUXT LTD. All Rights Reserved.

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

bidi's People

Contributors

deno2 avatar malcolmsparks avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.