Coder Social home page Coder Social logo

bidi's Introduction

bidi

Join the chat at https://gitter.im/juxt/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. If you are serving REST resources, you should be providing links to other resources, and without full support for forming 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.

For example, suppose you wanted to use the same set of routes in your application and in your production Nginx or HAProxy configuration. Having your routes defined in a single data structure means you can programmatically generate your configuration, making your environments easier to manage and reducing the chance of discrepancies.

bidi also avoids 'terse' forms for the route definitions- reducing the number of parsing rules for the data structure is valued over convenience for the programmer. Convenience can always be added later with macros.

Finally, 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.

Comparison with other routing libraries

There are numerous Clojure(Script) routing libraries. Here's a table to help you compare.

Library clj cljs Syntax Isomorphic? Self-contained? Extensible?
Compojure Macros
Moustache Macros
RouteOne Macros
Pedestal Data
gudu Data
secretary Macros
silk Data
fnhouse Macros
bidi Data
reitit Data

bidi is written to do 'one thing well' (URI dispatch and formation) and is intended for use with Ring middleware, HTTP servers (including Jetty, http-kit and aleph) and is fully compatible with Liberator.

If you're using with Liberator, see #95 for some more details on how to use them together.

Installation

Add the following dependency to your project.clj file

Clojars Project Build Status

As bidi uses Clojure's reader conditionals, bidi is dependent on both Clojure 1.7 and Leiningen 2.5.3 or later.

Version 2.x

Version 2.x builds on 1.x by providing a mechanism to envelope multiple virtual hosts with a single route map. The idea is to eventually create a route map which defines routes across multiple services and helps with the construction of URIs to other services, a process which is traditionally error-prone.

Version 2.x is backward compatible and forward compatible with version 1.x. If you are upgrading from 1.x to 2.x you will not need to change your existing route definitions.

Take 5 minutes to learn bidi (using the REPL)

Let's create a route that matches /index.html. A route is simply a pair, containing a pattern and a result.

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

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

user> (use 'bidi.bidi)
nil
user> (match-route route "/index.html")
{: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 route "/another.html")
nil

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

Now, let's go in the other direction.

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

We ask bidi to use the same route definition to tell us the path that would match the :index handler. In this case, it tells us /index.html. So if you were forming 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 resilience to changes in the organisation of routes during development).

Multiple routes

Now let's suppose we have 2 routes. We match partially on their common prefix, which in this case is "/" but we could use "" if there were no common prefix. The patterns for the remaining path can be specified in a map (or vector of pairs, if order is important).

user> (def my-routes ["/" {"index.html" :index
                           "article.html" :article}])
#'user/my-routes

Since each entry in the map is itself a route, you can nest these recursively.

user> (def my-routes ["/" {"index.html" :index
                           "articles/" {"index.html" :article-index
                                        "article.html" :article}}])
#'user/my-routes

We can match these routes as before :-

user> (match-route my-routes "/index.html")
{:handler :index}
user> (match-route my-routes "/articles/article.html")
{:handler :article}

and in reverse too :-

user> (path-for my-routes :article-index)
"/articles/index.html"

Route patterns

It's common to want to match on a pattern or template, extracting some variable from the URI. Rather than including special characters in strings, we construct the pattern in segments using a Clojure vector [:id "/article.html"]. This vector replaces the string we had in the left hand side of the route pair.

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

Now, when we match on an article path, the keyword values are extracted into a map.

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

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

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

If you don't specify a required parameter an exception is thrown.

Apart from a few extra bells and whistles documented in the rest of this README, that's basically it. Your five minutes are up!

Verbose syntax

bidi also supports a verbose syntax which "compiles" to the more terse default syntax. For example:

(require '[bidi.verbose :refer [branch param leaf]])

(branch
 "http://localhost:8080"
 (branch "/users/" (param :user-id)
         (branch "/topics"
                 (leaf "" :topics)
                 (leaf "/bulk" :topic-bulk)))
 (branch "/topics/" (param :topic)
         (leaf "" :private-topic))
 (leaf "/schemas" :schemas)
 (branch "/orgs/" (param :org-id)
         (leaf "/topics" :org-topics)))

Will produce the following routes:

["http://localhost:8080"
 [[["/users/" :user-id]
   [["/topics" [["" :topics] ["/bulk" :topic-bulk]]]]]
  [["/topics/" :topic] [["" :private-topic]]]
  ["/schemas" :schemas]
  [["/orgs/" :org-id] [["/topics" :org-topics]]]]]

Going further

Here are some extra topics you'll need to know to use bidi in a project.

Wrapping as a Ring handler

Match results can be any value, but are typically functions (either in-line or via a symbol reference). You can easily wrap your routes to form a Ring handler (similar to what Compojure's routes and defroutes does) with the make-handler function.

(ns my.handler
  (:require [bidi.ring :refer (make-handler)]
            [ring.util.response :as res]))

(defn index-handler
  [request]
  (res/response "Homepage"))

(defn article-handler
  [{:keys [route-params]}]
  (res/response (str "You are viewing article: " (:id route-params))))

(def handler
  (make-handler ["/" {"index.html" index-handler
                      ["articles/" :id "/article.html"] article-handler}]))

To chain this with middleware is simple.

(ns my.app
  (:require [my.handler :refer [handler]]
            [ring.middleware.session :refer [wrap-session]
            [ring.middleware.flash :refer [wrap-flash]))
(def app
  (-> handler
      wrap-session
      wrap-flash))

Regular Expressions

We've already seen how keywords can be used to extract segments from a path. By default, keywords only capture numbers and simple identifiers. This is on purpose, in a defence against injection attacks. Often you'll want to specify exactly what you're trying to capture using a regular expression.

If we want :id to match a number only, we can substitute the keyword with a pair, containing a regular expression followed by the keyword. For example, instead of this :-

    [ [ "foo/" :id "/bar" ] :handler ]

we write this :-

    [ [ "foo/" [ #"\d+" :id ] "/bar" ] :handler ]

which would match the string foo/123/bar but not foo/abc/bar.

Advanced topics

These features are optional, you don't need to know about them to use bidi, but they may come in useful.

Guards

By default, routes ignore the request method, behaving like Compojure's ANY routes. That's fine if your handlers deal with the request methods themselves, as Liberator's do. However, if you want to limit a route to a request method, you can wrap the route in a pair (or map entry), using a keyword for the pattern. The keyword denotes the request method (:get, :put, etc.)

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

You can also restrict routes by any other request criteria. 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.

In this example, the /zip route is only matched if the server name in the request is juxt.pro. You can use this feature to restrict routes to virtual hosts or HTTP schemes.

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

Values in the guard map can be values, sets of acceptable values, or even predicate functions to give fine-grained control over the dispatch criteria.

Keywords

Sometimes you want segments of the URI to be extracted as keywords rather than strings, and in the reverse direction, to use keywords as values to be encoded into URIs.

You can construct a pattern similarly to how you specify regular expressions but instead of the regex you use specify keyword core function.

   [ "foo/" [ keyword :db/ident ] "/bar" ]

When matching the path foo/bidi/bar, the :route-params of the result would be {:db/ident :bidi}. To construct the path, you would use (path-for my-routes handler :db/ident :bidi), which results in foo/bidi/bar (the colon of the stringified keyword is omitted).

Namespaced keywords are also supported. Note that in the URI the / that separates the keyword's namespace from its name is URL encoded to %2F, rather than /.

Catch-All Routes

Note that you can use the pattern true to match anything. This is useful for writing catch-all routes.

For example, if we'd like to match a certain set of routes and return 404 Not Found for everything else, we can do the following:

(def my-routes ["/" [["index.html" :index]
                  [true         :not-found]]])

We used vectors rather than maps to define the routes because the order of the definitions is significant (i.e. true will completely subsume the other routes if we let it).

Now let's try to match on that:

user> (match-route my-routes "/index.html")
{:handler :index}
user> (match-route my-routes "/other.html")
{:handler :not-found}

Note that :not-found doesn't have any special significance here--we still need to provide a hander function that implements the desired 404 behavior.

Route definitions

A route is formed as a pair: [ <pattern> <matched> ]

The left-hand-side of a pair is the pattern. It can match a path, either fully or partially. The simplest pattern is a string, but other types of patterns are also possible, including segmented paths, regular expressions, records, in various combinations.

The right-hand-side indicates the result of the match (in the case that the pattern is matched fully) or a route sub-structure that attempts to match on the remainder of the path (in the case that the pattern is matched partially). The route structure is a recursive structure.

This BNF grammar formally defines the basic route structure, although it is possible extend these definitions by adding types that satisfy the protocols used in bidi (more on this later).

RouteStructure := RoutePair

RoutePair ::= [ Pattern Matched ]

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

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+ }

In case of confusion, refer to bidi examples found in this README and in the test suite.

A schema is available as bidi.schema/RoutePair. You can use this to check or validate a bidi route structure in your code.

(require '[schema.core :as s] bidi.schema)

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

;; Check that the route is properly structured - returns nil if valid;
;; otherwise, returns a value with 'bad' parts of the route.
(s/check bidi.schema/RoutePair route)

;; Throw an exception if the route is badly structured
(s/validate bidi.schema/RoutePair route)

Virtual Hosts

If you are serving multiple virtual hosts with the same server, you may want to create a super-structure that allows routing across virtual host boundaries.

Here's a virtual-host structure:

["https://example.org:8443"
 ["/index.html" :index]
 ["/login" :login]
 ["/posts" […]]

It's just like the vector-of-vectors syntax we've seen before in bidi, but this time the first element is a virtual-host declaration. This is usually a string but can also be a java.net.URI or java.net.URL, or a map like {:scheme :https :host "example.org:8443"}.

A virtual-hosts super-structure is created with the bidi.vhosts/vhosts.model variadic function, each argument is a virtual-host structure.

(require '[bidi.vhosts :refer [vhosts-model]])

(def my-vhosts-model
  (vhosts-model ["https://example.org:8443"
                 ["/index.html" :index]
                 ["/login" :login]]

                ["https://blog.example.org"
                 ["/posts.html" […]]]))

uri-info

When using virtual hosts, use the bidi.vhosts/uri-info to generate a map of URIs.

For example:

(uri-info my-vhosts-model :index {:query-params {"q" "juxt"}})

would return

{:uri "https://example.org:8443/index.html?q=juxt"
 :path "/index.html"
 :host "example.org:8443"
 :scheme :https
 :href "https://example.org:8443/index.html?q=juxt"}

A partially applied uri-info function is available in bidi's matching context and returns a map of the following elements. This partial applies the vhosts-model which can help with dependency cycles in your code (where your bidi router requires knowledge of resources, which have views that require knowledge of the bidi router's routes).

When called via bidi's match-context, the :href entry in the result may not contain the scheme, host and port, if these are redundant, whereas the :uri entry always contains an absolute URI. If you are creating HTML content for a browser, :href is safe to use. If, for example, you are creating an API returning a JSON-formatted response body, prefer :uri.

Synonymous virtual-hosts

The virtual-host declaration can itself be a vector, if you need to match multiple possibilities. Here's another example, which matches two hosts:

[["https://example.org:8443" "http://example.org:8000"]
 ["/index.html" :index]
 ["/login" :login]]

The rules for uri-info are that the first virtual-host in the vector is used. When the request is known to bidi (i.e. in the partially applied uri-info function in the match-context) the algorithm chooses the first virtual host that matches the request URI's scheme.

Wildcards

An virtual host can be specified as a wildcard :*, which means it matches any scheme/host. Calls to uri-info will assume the scheme/host are that of the incoming request.

[:*
 ["/index.html" :index]
 ["/login" :login]]

Wildcards can be mixed with other vhost forms.

Composability

As they are simply nested data structures (strings, vectors, maps), route structures are highly composable. They are consistent and easy to generate. A future version of bidi may contain macros to reduce the number of brackets 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.

Built-in records are available but you can also create your own. Below is a description of the built-in ones and should give you an idea what is possible. If you add your own types, please consider contributing them to the project. Make sure you test that your types in both directions (for URI matching and formation).

Redirect

The Redirect record is included which satisfies the Matched protocol.

Consider the following route definition.

(defn my-handler [req] {:status 200 :body "Hello World!"})

["/articles" {"/new" my-handler
              "/old" (->Redirect 307 my-handler)}]

Any requests to /articles/old yield 307 Temporary Redirect responses with a Location header of /articles/new. This is a robust way of forming redirects in your code, since it guarantees that the Location URI matches an existing handler, both reducing the chance of broken links and encouraging the practise of retaining old URIs (linking to new ones) after refactoring. You can also use it for the common practice of adding a welcome page suffix, for example, adding index.html to a URI ending in /.

Resources and ResourcesMaybe

The Resources and ResourcesMaybe record can be used on the right-hand side of a route. It serves resources from the classpath. After the pattern is matched, the remaining part of the path is added to the given prefix.

["/resources" (->ResourcesMaybe {:prefix "public/"})

There is an important difference between Resources and ResourcesMaybe. Resources will return a 404 response if the resource cannot be found, while ResourcesMaybe will return nil, allowing subsequent routes to be tried.

Files

Similar to Resources, Files will serve files from a file-system.

["pics/" (->Files {:dir "/tmp/pics"})]

WrapMiddleware

You can wrap the target handler in Ring middleware as usual. But sometimes you need to specify that the handlers from certain patterns are wrapped in particular middleware.

For example :-

(match-route ["/index.html" (->WrapMiddleware handler wrap-params)]
             "/index.html")

Use this with caution. If you are using this you are probably doing it wrong.

Bidi separates URI routing from request handling. Ring middleware is something that should apply to handlers, not routes. If you have a set of middleware common to a group of handlers, you should apply the middleware to each handler in turn, rather than use ->WrapMiddleware. Better to map a middleware applying function over your handlers rather than use this feature.

Alternates

Sometimes you want to specify a list of potential candidate patterns, which each match the handler. The first in the list is considered the canonical pattern for the purposes of URI formation.

[#{"/index.html" "/index"} :index]

Any pattern can be used in the list. This allows quite sophisticated matching. For example, if you want to match on requests that are either HEAD or GET but not anything else.

[#{:head :get} :index]

Or match if the server name is juxt.pro or localhost.

[#{{:server-name "juxt.pro"}{:server-name "localhost"}}
 {"/index.html" :index}]

Tagged Match

Sometimes you need to apply a tag to a route, so you can use the tag (rather than the handler) in a path-for function. This is very convenient when forming routes, because you don't need to have a reference to the handler itself.

You can use the tag function to construct these records.

(tag my-handler :my-tag)

It's common to use the single threaded macro, so wrapping handlers in tags is just like wrapping them in Ring middleware. For example :-

["/" [["foo" (-> foo-handler (tag :foo)]
      [["bar/" :id] (-> bar-handler (tag :bar)]]]

Paths can now be created like this :-

(path-for my-routes :foo)
(path-for my-routes :bar :id "123")

Route sequences

It's possible to extract all possible routes from a route structure with route-seq.

Call route-seq on a route structure returns a sequence of all the possible routes contained in the route structure. This is useful to generating a site map. Each route is a map containing a path and a handler entry.

If you use keywords to extract route parameters, they will be contained in the path. If you wish to control the expansion, use a custom record that satisfies both bidi/Pattern and bidi/Matches.

Contributing

We welcome pull requests. If possible, please run the tests and make sure they pass before you submit one.

$ lein test

lein test bidi.bidi-test

lein test bidi.perf-test
Time for 1000 matches using Compojure routes
"Elapsed time: 17.645077 msecs"
Time for 1000 matches using uncompiled bidi routes
"Elapsed time: 66.449164 msecs"
Time for 1000 matches using compiled bidi routes
"Elapsed time: 21.269446 msecs"

Ran 9 tests containing 47 assertions.
0 failures, 0 errors.

A big thank you to everyone involved in bidi so far, including

  • Alexander Kiel
  • Bobby Calderwood
  • Cameron Desautels
  • Chris Price
  • David Thomas Hume
  • Dene Simpson
  • Dominic Monroe
  • Elben Shira
  • James Henderson
  • Jeff Rose
  • John Cowie
  • Julian Birch
  • Malcolm Sparks
  • Martin Trojer
  • Matt Mitchell
  • Michael Sappler
  • Nate Smith
  • Neale Swinnerton
  • Nicolas Ha
  • Oliver Hine
  • Philipp Meier
  • Rob Mather
  • Sebastian Bensusan
  • Thomas Crowley
  • Thomas Mulvaney
  • Tom Crayford
  • Andrew Phillips

Copyright & License

The MIT License (MIT)

Copyright © 2014-2015 JUXT LTD.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

bidi's People

Contributors

4xrsjcr9 avatar alexanderkiel avatar andreacrotti avatar camdez avatar cprice404 avatar damn avatar deno2 avatar dijonkitchen avatar dthume avatar elben avatar ericnormand avatar fromheten avatar ggeoffrey avatar griff avatar jarohen avatar jeroenheijmans avatar jeroenvandijk avatar jethrokuan avatar johncowie avatar jstepien avatar malcolmsparks avatar martintrojer avatar nwjsmith avatar oliyh avatar rosejn avatar severeoverfl0w avatar tcrayford avatar theasp avatar wavejumper avatar winks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bidi's Issues

Migrate from .cljx to .cljc

Now that Clojure 1.7.0 is released, we need to move from .cljx to .cljc files.

I have done the initial port on the cljc branch. However, I somehow can't get the tests to work which I believe will be an issue until the next Leiningen is released.

Lazily I'm hoping that time alone solves this issue, but we could move to something like boot.

Update to ring 1.4.0

This is half feature request, half knowledge base in case someone else runs into the same problem.

Simply adding bidi 1.21.0 or 1.20.3 to a project that uses ring 1.4.0 causes a Null Pointer Exception when you attempt to run ring or the repl. You don't need to be using bidi yet for the error to appear.

The exception will be logged along the lines of:

#error {
 :cause nil
 :via
 [{:type clojure.lang.Compiler$CompilerException
   :message java.lang.NullPointerException, compiling:(ring/util/http_response.clj:1068:1)
   :at [clojure.lang.Compiler load Compiler.java 7239]}
  {:type java.lang.NullPointerException
   :message nil
   :at [clojure.core$with_meta__4146 invoke core.clj 218]}]
 :trace
 [[clojure.core$with_meta__4146 invoke core.clj 218]
  [potemkin.namespaces$import_def invoke namespaces.clj 67]
  [clojure.lang.Var invoke Var.java 394]
  [clojure.lang.AFn applyToHelper AFn.java 165]
  [clojure.lang.Var applyTo Var.java 700]
...
]
}

The only namespace referenced from your project will likely be your handler, on line 1. Adding the dependency as [bidi "1.21.0" :exclusions [ring/ring-core]] solved the compiler exception (although I've yet to integrate it).

BNF bug: Matched can't be RoutePair

The route definition BNF grammar claims a Matched can be (among other options) either a RoutePair or a vector of many RoutePairs. The current implementation, however, doesn't allow the former:

(match-route "/index" ["/" ["index" :index]])
;; IllegalArgumentException No implementation of method: :match-pattern
;; of protocol: #'bidi.bidi/Pattern found for class: nil

while the latter is okay:

(match-route "/index" ["/" [["index" :index]]])
;;=> {:handler :index}

Given that RoutePairs are vectors themselves, I think it's wiser to drop the convenience and always require the [ RoutePair+ ] form – or even [ RoutePair* ].

Changelog?

Hi Malcolm - is there a quick way (short of going through the Git history) of finding breaking changes between versions of bidi?

Cheers,

James

Why do you advise against ->WrapMiddleware?

(Not saying it's wrong btw, just that I don't understand it!)

In the README, you say that if you feel the need to use ->WrapMiddleware, 'you're probably doing it wrong'. I agree with the reasoning that it's good to split apart URI routing from request handling, but I usually wrap all my API routes with middleware like 'wrap-params' or 'wrap-restful-format'. It seems a bit overkill to apply those middleware to every handler individually. What would you recommend? Am I doing it wrong?

Cheers,

James

Emails in path are not allowed by default

I came across a situation where I needed to create an api with a path like "/foo/by-email/[email protected]" where the email is a route param. It appears bidi does not let you do that currently without a regex specification of the email. Please see example below:

(deftest email-in-path-test
  (testing "fails without regex"
    (let [route [["/foo/" :email] :foo]]
      (is (= (match-route route (path-for route :foo :email "[email protected]"))
             {:handler :foo
              :route-params {:email "[email protected]"}}))))
  (testing "passes with regex"
    (let [route [["/foo/" [#".+\@.+\..+" :email]] :foo]]
      (is (= (match-route route (path-for route :foo :email "[email protected]"))
             {:handler :foo
              :route-params {:email "[email protected]"}})))))

Not sure if this is intended or just an oversight.

Using a regex to specify format is fine, but I'm using it together with yada and it thinks the regex is a path element.

Files doesn't do reverse match.

This works fine:

(def app (-> ["/" (->Files {:dir files-path})]
             (compile-route)
             (make-handler)))

I have a test.txt and my server responds with its contents when I do GET / on it.

But this gives a 404:

(def files-path "/home/muhuk/tmp/")

(def routes (compile-route ["/" :files]))

(def handlers {:files (->Files {:dir files-path})})

(def app (make-handler routes handlers))

Also I am curious if this is expected behavior:

(bidi.bidi/path-for routes :files) ;; => "/"
(bidi.bidi/match-route routes "/") ;; => {:handler :files}
(bidi.bidi/match-route routes "/test.txt") ;; => nil

Shouldn't the third call resolve to {:handler :files :something-else "test.txt"} ?


Assuming I want to serve a limited number of static files, should I use Files or would it make more sense to take the handler part of it and cook my own view?

;; This part:
(-> (fn [req] (url-response res))
    (wrap-file-info (:mime-types options))
    (wrap-content-type options)
    (wrap-not-modified))

So many dependencies?

Was thinking about using bidi as I need some routing for both the server and client sides of an app. When I looked at the dependency tree (lein deps tree):

$ lein with-profile production deps :tree

I found this:

 [bidi "1.19.0" :exclusions [[commons-codec]]]
   [com.cemerick/url "0.1.1"]
     [pathetic "0.5.0"]
       [com.cemerick/clojurescript.test "0.0.4"]
   [org.clojure/clojurescript "0.0-2371"]
     [com.google.javascript/closure-compiler "v20140625"]
       [args4j "2.0.26"]
       [com.google.code.findbugs/jsr305 "1.3.9"]
       [com.google.javascript/closure-compiler-externs "v20140625"]
       [com.google.protobuf/protobuf-java "2.5.0"]
       [org.json/json "20090211"]
     [org.clojure/data.json "0.2.3"]
     [org.clojure/google-closure-library "0.0-20140718-946a7d39"]
       [org.clojure/google-closure-library-third-party "0.0-20140718-946a7d39"]
     [org.mozilla/rhino "1.7R4"]
   [ring/ring-core "1.2.1"]
     [clj-time "0.4.4"]
       [joda-time "2.1"]
     [commons-fileupload "1.3"]
     [commons-io "2.4"]
     [ring/ring-codec "1.0.0"]

My main concern is making an uberjar, which, as a result of bidi, will contain all these things I'm not using (when running from an uberjar), including ClojureScript, clojurescript-test, ring, and so on. Should those be marked as dev dependencies or "provided" or something like that? I suppose I can just go with my own :exclusions clauses, but it seems like the library itself shouldn't require those, right?

I can see you've got a bidi.ring namespace (is there a clojure-doc generated somewhere?) which I suppose requires ring, but what happened to the "one thing well" story! ;) (I can't tell what make-handler does. Return a function that takes a request and .... The routes data has to have function references for handlers, not keywords as shown in the readme example, right?

Anyway.... ;)

Catch all

Any method for making a route to catch all requests?

Activate Travis CI

You accepted a PR for the .travis.yml file but never got round to enabling Travis CI for the repo, going to https://travis-ci.org/profile/juxt should guide you to enabling it, this will automatically run tests for pull requests, which is very handy!

Support compojure's `context`

Although not strictly part of the ring spec, it would be nice if make-handler was composable with compojure's context macro.

Redirects should reset uri param parsing.

Using bidi 1.10.2

I want to normalise uris wrt trailing slashes, with something like

 [["programmes/" :programme-id "/projects/"] projects)]
 [["programmes/" :programme-id "/projects"] (->Redirect 307 projects)]

but requests to programmes/21314/projects fail with

clojure.lang.ExceptionInfo: No parameter found in params for key :programme-id {}
at clojure.core$ex_info.invoke(core.clj:4327)
at bidi.bidi$eval5392$fn__5393.invoke(bidi.clj:124)
at bidi.bidi$eval5309$fn__5332$G__5294__5339.invoke(bidi.clj:69)
at bidi.bidi$eval5541$fn__5542$fn__5543.invoke(bidi.clj:220)
at clojure.core$map$fn__4207.invoke(core.clj:2485)

I believe that on redirect bidi should somehow 'reset' the parsing of the uri params?

resources fails with make-handler?

create project

lein new bidiresources
cd bidiresources

mkdir -p resources/public/
echo "hello world" > resources/public/index.html

projects.clj

(defproject bidiresources "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :main bidiresources.core
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [bidi "1.21.1"]
                 [org.immutant/web "2.1.1"]])

bidiresources/core.clj

(ns bidiresources.core
  (:require
    [bidi.ring :refer [make-handler resources]]
    [immutant.web :as web])
  (:gen-class))

(def req-handlers {:resources (resources {:prefix "public/"})})
(def routes ["/ui" (resources {:prefix "public/"})])
(def app (make-handler routes req-handlers))

(defn -main []
  (web/run app))

Doing

curl -v http://localhost:8080/ui/index.html

Fails with:

java.lang.IllegalArgumentException: No implementation of method: :request of protocol: #'bidi.ring/Ring found for class: nil
        at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:554) ~[clojure-1.7.0.jar:na]
        at bidi.ring$eval2571$fn__2572$G__2562__2581.invoke(ring.clj:13) ~[na:na]
        at bidi.ring$make_handler$fn__2601.invoke(ring.clj:39) ~[na:na]
        at immutant.web.internal.undertow$create_http_handler$reify__6011.handleRequest(undertow.clj:135) ~[na:na]
        at org.projectodd.wunderboss.web.undertow.async.websocket.UndertowWebsocket$2.handleRequest(UndertowWebsocket.java:104) ~[wunderboss-web-undertow-0.10.0.jar:na]
        at io.undertow.server.session.SessionAttachmentHandler.handleRequest(SessionAttachmentHandler.java:68) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
        at io.undertow.server.Connectors.executeRootHandler(Connectors.java:199) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
        at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:778) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_66-internal]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_66-internal]
        at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_66-internal]

if I change

(def routes ["/ui" (resources {:prefix "public/"})])

for

(def routes ["/ui" :resources])

I get

java.lang.NullPointerException: Ring handler returned nil
        at immutant.web.internal.undertow$create_http_handler$reify__6011.handleRequest(undertow.clj:138) ~[na:na]
        at org.projectodd.wunderboss.web.undertow.async.websocket.UndertowWebsocket$2.handleRequest(UndertowWebsocket.java:104) ~[wunderboss-web-undertow-0.10.0.jar:na]
        at io.undertow.server.session.SessionAttachmentHandler.handleRequest(SessionAttachmentHandler.java:68) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
        at io.undertow.server.Connectors.executeRootHandler(Connectors.java:199) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
        at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:778) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_66-internal]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_66-internal]
        at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_66-internal]

Example of using path-for for post->get redirects?

Hi,

I'm playing around with bidi and liking it so far! I've become slightly confused about the correct way to do redirects after posts. I'm using liberator and I want to be able to form the location header from my bidi route (typically my resources will redirect to themselves following a post request, to play nicely with browsers).

I think I want to use 'path-for' to get the path for my redirect but the problem I'm having is that my route definitions and handlers aren't visible inside my liberator resources. I'm not too familiar with ring or liberator so I'm probably missing something obvious. Would love a pointer or two!

Thanks.

matching routes with CompiledPrefix fails

Hi,

This code throws an exception. Is this kind of (prefix) route supported?

(bidi/match-route
 (bidi/compile-route
  [["/base/path/" [#".+" :id]] {:get :match!}])
 "/base/path/1"
 :request-method :get)

java.lang.IllegalArgumentException: No implementation of method: :segment-regex-group of protocol: #'bidi.bidi/PatternSegment found for class: bidi.bidi.CompiledPrefix
core_deftype.clj:544 clojure.core/-cache-protocol-fn
bidi.clj:69 bidi.bidi/eval13078[fn]
core.clj:2557 clojure.core/map[fn]
LazySeq.java:40 clojure.lang.LazySeq.sval
LazySeq.java:49 clojure.lang.LazySeq.seq
RT.java:484 clojure.lang.RT.seq
core.clj:133 clojure.core/seq
core.clj:2551 clojure.core/map[fn]
LazySeq.java:40 clojure.lang.LazySeq.sval
LazySeq.java:49 clojure.lang.LazySeq.seq
RT.java:484 clojure.lang.RT.seq
core.clj:133 clojure.core/seq
protocols.clj:26 clojure.core.protocols/seq-reduce
protocols.clj:53 clojure.core.protocols/fn
protocols.clj:13 clojure.core.protocols/fn[fn]
core.clj:6287 clojure.core/reduce
bidi.clj:200 bidi.bidi/eval13310[fn]
bidi.clj:148 bidi.bidi/eval13210[fn]
bidi.clj:164 bidi.bidi/match-pair
bidi.clj:286 bidi.bidi/match-route

Unclear how to capture all unmatched route as a param; regex has bizarre behavior

Given a route that partially matches, I want to pass whatever remains after initial matching to a function.

More context: My app is backed by a a database, which stores documents by relative path -- say, app/index.html is the name of an html document I want to return. Given two paths, /root/app/index.html and /root/app/img/image.png, I want to pass app/index.html or app/img/image.png in to a function to load them from the DB. So far, this seems totally impossible with bidi.

Thought one: just match it with a keyword:

> (bidi.bidi/match-route ["/" {"root/" {[:remaining] :handler}}] "/root/app/img/image.png")
;;=> nil

Okay, no dice. So thought two: what about a regex? This... is where things get real weird:

> (bidi.bidi/match-route ["/" {"root/" {[#"(?s).+":remaining] handler}}] "/root/app/img/image.png")
;;=> {:route-params {:remaining "g"}, :handler :handler}

;; Okay weird -- it only got the last letter. How about we use a capture group? 

> (bidi.bidi/match-route ["/" {"root/" {[#"(?s)(.+)" :remaining] :handler}}] "/root/app/img/image.png")
;;=> {:route-params {:remaining "app/img/image.pn"}, :handler :handler}

The regex winds up receiving everything but the final letter. It looks like the string is being chomped before it's even passed to the regex:

> (bidi.bidi/match-route ["/" {"root/" {[#"(app/img/image\.png)" :remaining] :handler}}] "/root/app/img/image.png")
;;=> nil

> (bidi.bidi/match-route ["/" {"root/" {[#"(app/img/image\.pn)" :remaining] :handler}}] "/root/app/img/image.png")
;;=> {:route-params {:remaining "app/img/image.pn"}, :handler :handler}

I'm on bidi 1.23.1, Clojure 1.7.0. Any thoughts?

Can't serve ->Resources from /target

I am using boot instead of Leiningen, and it treats /resources directory as read-only by creating final artifacts in /target.

How could I serve index.html from /target/index.html with bidi?

This only serves index.html from /resources/public/:

(def routes
  ["" [["" (->Resources {:prefix "public/"})]]])

Unclear how to use bidi.ring/redirect-after-post

The following WILL redirect after a post:

(br/redirect-after-post my-handler)

However, I don't really see any use for this, as a post implies some actions with parameters.

Here is how one might naively use the redirect:

(fn [req]
  ;; do some stuff
  (br/redirect-after-post my-handler))

Unfortunately, this returns an error:

bidi.ring.Redirect cannot be cast to clojure.lang.IFn

So, how does one get his hands on the parameters before the redirection?

Regular Expression matches not working?

Matching with a regular expression does not seem to be working, unless I am misunderstanding something.

Two cases:

(match-route [[#"/x+" :x] :x-match] "/xxx") => {:handler :x-match, :route-params {:x "x"}}

Note that only one character from the "xxx" ended up in route-params.

(match-route [[#"/x+" :x] :x-match] "/x") => nil

This should match the single "x" in the path.

Wildcards?

Hi,

I'd like to match a capture multiple path segments in a single param, a kind of wildcard:

(let [routes ["/" {"" status/status
"com/" {"api/" {[#"(.)*" :path] handle-wildcard}}}]]

(bidi/match-route routes "/com/api/proxy/what-ever/here"))

I would like the result of that to be:

{:handler handle-wildcard :params {:path "proxy/what-ever/here"}

Is this possible? Since everything is segmented, I can't think of how this would be done.

Shouldn't the doc mention tag instead of ->TaggedMatch

The source shows a function tag.

It is easier to type (and much prettier) than ->TaggedMatch.

Shouldn't its use be encouraged in the readme?
->TaggedMatch feels like an implementation detail.

Edit: the same is true for ->Atlernates.

uuid type error

I'm passing UUIDs as (potentially, not always) deserialized by Transit, which has its own UUID type (with the same printed representation as cljs.core.UUID). Bidi fails to generate a route, saying that it's not compatible with the route type.

You can see that dnolen recently added checks inside of its uuid? fn for both. I'm not sure what the answer here is (Bidi probably shouldn't have to be aware of every other UUID type), but it's certainly causing some problems locally.

Non-capturing groups support for match-routes

Hello,

First let me start by expressing my thanks for the time and effort spent creating bidi. We use enjoy bidi as part of our routing.

There is something I came to notice about route matching which I think that could be improved. Often one will have to create routes with an optional param like a page postfix so that paths such as "/foo" and "/foo/page-2" are recognised.

As far as I understand this can be achieved like so:

Without page:

user> (bidi/match-route ["/foo" :foo]
                        "/foo")
{:handler :foo}

With page:

user> (bidi/match-route [["/foo" "/page-" :page] :foo]
                        "/foo/page-1")
{:handler :foo, :params {:page "1"}}

Both combined:

user> (map (partial bidi/match-route ["/foo" {"" :foo 
                                              ["/page-" :page] :foo}])
           ["/foo" "/foo/page-2"])
({:handler :foo} {:handler :foo, :params {:page "2"}})

However, since there is support for regex I tried to write a more compact version by using non-capturing groups to exclude irrelevant parts such "/page-" like so:

user> (map (partial bidi/match-route 
                    [["/foo" [#"(?:/page-(\d+))?" :page]] :foo])
           ["/foo" "/foo/page-2"])
({:handler :foo, :params {:page ""}} 
 {:handler :foo, :params {:page "/page-2"}})

Apparently the capturing and non-capturing groups are ignored. What I was hoping for was something like the previous result:

({:handler :foo} {:handler :foo, :params {:page "2"}})

I'm not sure if it's worth the effort but such a change might be helpful in reducing certain redundancies when defining routes with optional path-params. What do you think?

Regards,

Roman

compile-route doesn't support regular expressions?

(let [r [["/foo/" [#".*" :stuff]] :foo]]
  (println (bidi/match-route r "/foo/with/lots/of/extra/stuff"))
  (bidi/compile-route r))
{:handler :foo, :route-params {:stuff with/lots/of/extra/stuff}}
IllegalArgumentException No implementation of method: :compile-segment of protocol: #'bidi.bidi/Compilable found for class: clojure.lang.PersistentVector  clojure.core/-cache-protocol-fn (core_deftype.clj:541)

path-for throws IllegalArgumentException with compiled routes

(def routes ["/" {"index.html" :index
                  "article.html" :article}])

(def compiled-routes (compile-route routes))

(path-for compiled-routes :index)

Throws

IllegalArgumentException No implementation of method: :unmatch-pattern of protocol: #'bidi.bidi/Pattern found for class: java.util.regex.Pattern  clojure.core/-cache-protocol-fn (core_deftype.clj:544)

Using bidi 1.18.10.

Limitations when using same handler for different routes

Hi,

First of all, thanks for a fantastic library, really enjoying using bidi with Liberator. The bi-directionality and routes as data is great!

I encountered a few issues when defining a route structure that has the same handler occurring multiple times. I'm not sure whether this is because it's a bad idea to do that, but let me show you the example I'm using:

(def routes
  ["/" [["posts" 'posts-handler]
        [["users/" :user "/posts"] 'posts-handler]]])

In this case, posts-handler understands queries both for all posts (first route), and for a specific users posts (second route).

Given this route structure, I would like to be able to generate URLs both for all posts, and a specific users posts like so:

(path-for routes 'posts-handler)
;; => "/posts"
(path-for routes 'posts-handler :user "blahonga")
;; => "/users/blahonga/posts"

ie. if params are given, routes where the params occur should be preferred. It may even make sense to fail completely if all params supplied are not used. I'm not sure.

Currently the unmatching takes the first route matching the given handler. However, the first case in the above path-for example actually blows up with clojure.lang.ExceptionInfo: Cannot form URI without a value given for :user parameter (full stack trace below). I believe this is due to non-lazyness (or lazy-seq chunking), unresolve-handler for PersistentVectors uses:

(first (keep #(match-pair % m) this))

Which seems to be not lazy for PersistentVectors.

Full stack trace:

clojure.lang.ExceptionInfo: Cannot form URI without a value given for :user parameter
 at clojure.core$ex_info.invoke (core.clj:4327)
    bidi.bidi$eval28675$fn__28678.invoke (bidi.clj:56)
    bidi.bidi$eval28612$fn__28624$G__28599__28631.invoke (bidi.clj:33)
    bidi.bidi$eval28811$fn__28812$fn__28813.invoke (bidi.clj:133)
    clojure.core$map$fn__4207.invoke (core.clj:2485)
    clojure.lang.LazySeq.sval (LazySeq.java:42)
    clojure.lang.LazySeq.seq (LazySeq.java:60)
    clojure.lang.RT.seq (RT.java:484)
    clojure.core$seq.invoke (core.clj:133)
    clojure.core$apply.invoke (core.clj:617)
    bidi.bidi$eval28811$fn__28812.invoke (bidi.clj:133)
    bidi.bidi$eval28711$fn__28725$G__28700__28732.invoke (bidi.clj:80)
    bidi.bidi$unmatch_pair.invoke (bidi.clj:152)
    bidi.bidi$eval28898$fn__28899$fn__28900.invoke (bidi.clj:160)
    clojure.core$keep$fn__6349.invoke (core.clj:6599)
    clojure.lang.LazySeq.sval (LazySeq.java:42)
    clojure.lang.LazySeq.seq (LazySeq.java:60)
    clojure.lang.LazySeq.first (LazySeq.java:82)
    clojure.lang.RT.first (RT.java:577)
    clojure.core$first.invoke (core.clj:55)
    bidi.bidi$eval28898$fn__28899.invoke (bidi.clj:160)
    bidi.bidi$eval28754$fn__28768$G__28743__28775.invoke (bidi.clj:87)
    bidi.bidi$unmatch_pair.invoke (bidi.clj:151)
    bidi.bidi$path_for.doInvoke (bidi.clj:207)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    bidi.bidi$eval32697.invoke (form-init1346470612871071144.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:6619)
    clojure.lang.Compiler.eval (Compiler.java:6582)
    clojure.core$eval.invoke (core.clj:2852)
    clojure.main$repl$read_eval_print__6588$fn__6591.invoke (main.clj:259)
    clojure.main$repl$read_eval_print__6588.invoke (main.clj:259)
    clojure.main$repl$fn__6597.invoke (main.clj:277)
    clojure.main$repl.doInvoke (main.clj:277)
    clojure.lang.RestFn.invoke (RestFn.java:1096)
    clojure.tools.nrepl.middleware.interruptible_eval$evaluate$fn__675.invoke (interruptible_eval.clj:56)
    clojure.lang.AFn.applyToHelper (AFn.java:159)
    clojure.lang.AFn.applyTo (AFn.java:151)
    clojure.core$apply.invoke (core.clj:617)
    clojure.core$with_bindings_STAR_.doInvoke (core.clj:1788)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invoke (interruptible_eval.clj:41)
    clojure.tools.nrepl.middleware.interruptible_eval$interruptible_eval$fn__716$fn__719.invoke (interruptible_eval.clj:171)
    clojure.core$comp$fn__4154.invoke (core.clj:2330)
    clojure.tools.nrepl.middleware.interruptible_eval$run_next$fn__709.invoke (interruptible_eval.clj:138)
    clojure.lang.AFn.run (AFn.java:24)
    java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1145)
    java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:615)
    java.lang.Thread.run (Thread.java:744)

How to handle root route? Not "/', but ""

Every Bidi example I saw was with a starting slash, like this:

(routes [_] ["/" {"index.html" ::index
                    "" (redirect ::index)}])

What about an homepage?
Say I'm going to www.my-website.com, how can I match it?

Modifying the routes to something like this doesn't work:

(routes [_] ["" {"/" {"index.html" ::index}
                   "" (redirect ::index)}])

You can reproduce the problem with the project created with:

lein new modular my-website bootstrap-cover

->Alternates with "" is broken

(def routes-test 
  ["/" {(->Alternates ["xxx" "index" "a"]) :handler}])

(bidi/match-route routes-test "/index")

=> {:handler :handler}

however,

(def routes-test 
  ["/" {(->Alternates ["xxx" "" "index" "a"]) :handler}])

(bidi/match-route routes-test "/index")

=> nil

and,

(bidi/match-route routes-test "/xxx")

=> {:handler :handler}

workaround,

(def routes-test 
  ["/" {(->Alternates ["xxx" #"$" "index" "a"]) :handler}])

(bidi/match-route routes-test "/xxx")
(bidi/match-route routes-test "/index")
(bidi/match-route routes-test "/")

all => {:handler :handler}

Coupling question

Hi there,

I'm just discovering your awesome library, and I have an architectural question about your usage in the real-world. Allow me to setup the problem with some theoretical code

(ns my.routes
  (:require [my.controllers.authentication :as auth]
                [cemerick.friend :as friend])

(def my-routes ["/logout" (wrap-middleware auth/logout friend/logout)

You can assume that I'm using bidi.ring/make-handler with my routes. Of course, route recognition and generation work marvelously. My question is one of coupling and indirection related to route generation (path-for). As I see it, there are two places where I might want to use path-for:

  1. inside "controller" / Ring handler functions (especially for redirections)
  2. in view-related code (I'm using Enlive) to update a hyperlink's HREF to point to the correct location.

It's the latter which concerns me. While it's natural for handlers to know about views (they have to render some representation), the converse is generally considered undesirable. But, if I have symbolic references to functions in the "dispatch" / handler position in my route definitions, then I need something along the lines of:

(path-for auth-routes auth/logout) 

in my view layer (Enlive snippets). Of course, we could solve the problem by introducing a layer of indirection such that instead of the bi-directional coupling being one of:

URL path <===> dispatch function

it could be:

URL path <===> *named route* / keyword <===> dispatch function

This would let us depend on an abstract name for route generation:

(path-for auth-routes :logout)

at the expense of complicating our route recognition where we would have to do two lookups to go from URL path to dispatch function. As this would mean opting out of your carefully written make-handler function, I naturally wanted to get your thoughts / advice and ask how you approach this problem in your production code.

Thanks both for the library and for reading this far.
Cheers!

Request-for

Would it be possible to explicitly assign an "ID" to a route? Let's say you wanted more than the path for generating a request. Something like:

;; Here, the "ID" would be the :update-post keyword:
(def my-routes [["/posts" :id] {:post [:update-post :my-handler]}])

(request-for my-routes :update-post :id "the-post-id")
;; => {:request-method :post :path "/posts/the-post-id" :handler :my-handler}

This would allow you to programmatically construct a full request using the route structure, not just the path.

Gotcha - mixing (make-handler routes handler-fn) with (bidi.ring/resources ...)

Hi Malcolm,

Have been playing around a fair bit with Bidi over the Christmas break - really like the separation between routes and handlers, thanks!

Thought I'd share a gotcha when using the 2-arg arity of bidi.bidi/make-handler - of course, may just be me not understanding Bidi's fully though! I had (something like) the following:

(:require [bidi.ring :as br])

(def routes
  ["" {"/" ::home-page-handler
       "/js" (br/resources {:prefix "js"})}])

(def handlers
  {::home-page-handler (fn [req]
                         (response "Hello world!"))})

(br/make-handler routes handlers)

This worked fine when requesting the "/" route, but failed with an NPE when trying to fetch anything under "/js", caused by ((handler-fn handler) ...) - the handler-fn was returning nil.

Looking into it a bit more deeply - the 1-arg version of make-handler defaults the handler-fn to 'identity', meaning that the handler returned by (br/resources ...) was being returned as-is to serve the resources, so it seems like it's not possible to mix a handler map with the built-in bidi.ring handlers?

I ended up solving it by altering the make-handler call, as follows:

(br/make-handler routes
                 (some-fn handlers
                          #(when (fn? %) %)))

to use my handlers when there was a match, but fall back on an 'identity-like' function when the routes already contained a request handler for the given route.

Feel free to do whatever you like with this issue - won't be offended if it's 'no action req'd, closed' - but if you've got any suggestions about how I can better use Bidi, I'd be very grateful!

Cheers,

James

Looks like bidi 1.7.0 on clojars doesn't actually work...

Exception in thread "main" java.lang.IllegalAccessError: url-response does not exist
    at clojure.core$refer.doInvoke(core.clj:3849)
    at clojure.lang.RestFn.applyTo(RestFn.java:139)
    at clojure.core$apply.invoke(core.clj:619)
    at clojure.core$load_lib.doInvoke(core.clj:5394)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:619)
    at clojure.core$load_libs.doInvoke(core.clj:5413)

Mime type not correctly guessed

In using ->ResourcesMaybe it seems that the mime-type for css isn't correctly guessed. In digging into the source, it looks like bidi uses wrap-file-info from ring.middleware.file-info. According to the discussion here: https://groups.google.com/forum/#!topic/ring-clojure/EmjsHw4QyEk. Use of wrap-file-info with resources is problematic. In my case, bidi sets the mime-type to be 'text/plain' and I see the following error in the browser console 'Resource interpreted as Stylesheet but transferred with MIME type text/plain: '

Nested routes throwing IllegalArgumentException

I have not been successful at nesting routes within other routes:

(def foo-routes
  ["/" [["bar" :bar]]])

(def app-routes
  ["/" [["foo" foo-routes]
        ["blah" :blah]]])

(comment
  ;; These should both match, and return :bar (I think)
  ;; With bidi 0.14 the first succeeds, but the second throws IllegalArgumentException
  (b/match-route foo-routes "/bar")
  (b/match-route app-routes "/foo/bar"))

The exception throw is:

IllegalArgumentException No implementation of method: :match-pattern of protocol: #'bidi.bidi/Pattern found for class: java.lang.Character

Why are query parameters not supported anymore?

I noticed that query parameters were removed in a commit in December. My requirement is that I need to be able to pass such parameters to be passed to all handlers without having to specify them in advance in the route definition.

It would be great if you could reintroduce the feature via a flag or expose an easy way to pre-parse the query parameters.

presence of query param breaks routing

ClojureScript:cljs.user> (bidi.bidi/match-route pp-web.routes/routes "/search")
{:handler :search}
ClojureScript:cljs.user> (bidi.bidi/match-route pp-web.routes/routes "/search?a=1")
nil

I see from searching the issues that matching on query-params isn't supported (as the handler ought to handle those not the router), but i expected it to still route properly ignoring the query param.

Protocol error on CLJS when matching routes on a map above a certain size

Hi Malcolm,

Really enjoying using Bidi on CLJS btw, it makes links/routing much easier :)

Ran into a problem when my routes structure goes beyond a certain size - while cljs.core.PersistentArrayMap satisfies the bidi.bidi/Matched protocol, cljs.core.PersistentHashMaps don't!

Have fixed for now by adding the following in my app, copying from the implementation for ArrayMap:

(extend-protocol bidi/Matched
  cljs.core.PersistentHashMap
  (resolve-handler [this m] (some #(bidi/match-pair % m) this))
  (unresolve-handler [this m] (some #(bidi/unmatch-pair % m) this)))

Guess it's a case of duplicating the implemention in Bidi too, or do you know of a cleaner solution?

Cheers :)

James

A nil handler will make path-for blow up

If you accidentally create a nil handler it will cause path-for to blow up if it traverses it

(def routes 
  ["/"
    {"foo" nil)
     "bar" :bar}])

(path-for routes :bar) 

This blows up as it attempts to traverse the foo structure.

IllegalArgumentException No implementation of method: :unresolve-handler of protocol: #'bidi.bidi/Matched found for class: nil  clojure.core/-cache-protocol-fn (core_deftype.clj:544)

Ideally bidi will blow up with a more helpful error message, indicating that the routes structure is wrong and the path it was traversing when it came across something unexpected.

identical routes w/different request-methods - first wins

Not sure if this is part of the design or not but I was surprised to find that bidi won't match a route if a previous route has the same path:

(bidi/match-route
 [["users" "/" [#"[^/]+" :id]] {:delete :delete-handler}
  ["users" "/" [#"[^/]+" :id]] {:put :update-handler}] "users/1" :request-method :put)

^^ yields nil

Not that one would normally construct routes like that. I just stumbled on this while dynamically building routes from a mapping.

  • Matt

->Alternates with similar prefixes is broken

(def routes-test
  ["/" {(->Alternates ["index" "index-x" "index.html" "x-index.html" "index-x.html" "x.html"]) :handler
        }])


(bidi/match-route routes-test "/index")         => {:handler :handler}
(bidi/match-route routes-test "/index-x")       => nil
(bidi/match-route routes-test "/index.html")    => nil
(bidi/match-route routes-test "/x.html")        => {:handler :handler}
(bidi/match-route routes-test "/x-index.html")  => {:handler :handler}
(bidi/match-route routes-test "/index-x.html")  => nil

More concise use of parameterized liberator resources?

I have some code:

(defresource foo [id]
  :available-media-types ["application/json"]
  :allowed-methods [:get]
  :handle-ok
  (fn [ctx]
    {:hello id}))

(def routes ["/foo/" {[:id] foo}])

(def app (-> (make-handler routes) (wrap-default api-default)))

This doesn't work as-is because something is trying to cast the foo function to an associative structure, so this seemed like it might work:

(def routes ["/foo/" {[:id] (fn [req] (foo (get-in req [:route-params :id])}])

But (foo x) produces a handler function that closes over x, it seems since once again something was trying to cast a function to an associative structure.

What I ended up with was:

(def routes ["/foo/" {[:id] (fn [req] ((foo (get-in req [:route-params :id]) req)}])

Which is kind of messy, especially when compared to something like Compojure:

(defroutes routes (ANY "/foo/:id" [id] (foo id)))

Am I missing something?

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.