Can anybody explain why Compojure's routing macro only accepts literal vector as route description parameter?

StackOverflow https://stackoverflow.com/questions/18542956

  •  26-06-2022
  •  | 
  •  

Frage

compojure.core/GET accepts as a route definition parameter either a string describing a URI or a vector describing a regular expression to be matched with parameters extracted from the URI in addition to the URI itself. You can find the docs here Routes in Detail

From my REPL (starting from scratch, leiningen's project.clj at the end of this question)

user> (use '[compojure.core :only [GET]])
nil
user> (def h (GET "/" [] "hello"))
#'user/h
user> (h {:uri "/" :request-method :get})
{:status 200, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "hello"}

so far so good, plain vanilla route

what follows is an example that uses a vector for routing:

user> (def h2 (GET ["/foo/:id" :id #"[0-9]+"] [] "hello from foo"))
#'user/h2
user> (h2 {:uri "/foo/123" :request-method :get})
{:status 200, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "hello from foo"}
user> (h2 {:uri "/foo/abc" :request-method :get})
nil

still all's good; you can see that it only matches requests for numerical :id's

now here comes the part I don't understand:

user> (def v ["/foo/:id" :id #"[0-9]+"])
#'user/v
user> (def h3 (GET v [] "hello again from foo"))
#'user/h3
user> (h3 {:uri "/foo/abc" :request-method :get})
IllegalArgumentException No implementation of method: :route-matches of protocol: #'clout.core/Route found for class: clojure.lang.PersistentVector  clojure.core/-cache-protocol-fn (core_deftype.clj:541)

Can anybody explain the reason why the handler fails at evaluation time when it's defined with a variable instead of a literal vector?

In case you want to replicate the exact environment, this is my leiningen project file project.clj:

(defproject cljlab "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"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/clojure-contrib "1.2.0"]
                 [compojure "1.1.5"]])
War es hilfreich?

Lösung

The issue is that compojure's GET is a macro, not a function. Macros run at compile time, so it doesn't get to see what is the value of "v". See the difference between the code generated for h2 and h3:

user> (pprint (macroexpand-1 '(GET ["/foo/:id" :id #"[0-9]+"] [] "hello from foo")))
      (compojure.core/make-route
      :get
      (clout.core/route-compile "/foo/:id" {:id #"[0-9]+"})
      (clojure.core/fn
         [request__1858__auto__]
         (compojure.core/let-request
           [[] request__1858__auto__]
           "hello from foo")))

user> (pprint (macroexpand-1 '(GET v [] "hello again from foo")))
      (compojure.core/make-route
      :get
      (if (clojure.core/string? v) (clout.core/route-compile v) v)
      (clojure.core/fn
        [request__1858__auto__]
        (compojure.core/let-request
          [[] request__1858__auto__]
          "hello again from foo")))

In the first case the macro is able to split the route into its constituents and transform it. In the second case the macro just sees the "v" symbol.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top