Use an empty path parameter in nested Compojure routes - clojure

How to match URLs without a trailing backspace in Compojure nested contexts?
Problem I'm trying to solve:
I want to match the following routes:
/users/
/users/:user-id
/users/:user-id/resources/
The first to access all users,
second for a single user entity,
last for all resources of a given user.
Using the following defroutes works:
(defroutes
(GET "/users/" ...)
(GET "/users/:user-id" ...)
(GET "/users/:user-id/resources/" ...))
However, as my API grow I want to use contexts to clarify the code.
(defroutes
(context "/users"
(users-routes) ;; All users operations
(context "/:user-id" [user-id]
(user-routes user-id) ;; All per-user operations
(context "/resources" []
(resources-routes user-id))))) ;; All user - resources operations
Which means that user-routes should look like:
(defn user-routes [user-id]
(routes
(GET "" [uid] ...)))
Which gives, at compile time:
clojure.lang.ExceptionInfo: Parse error in route string
data: {:failure
{:index 0,
:reason
[{:tag :regexp, :expecting #"(https?:)?//"}
{:tag :string, :expecting ":"}
{:tag :string, :expecting "*"}
{:tag :regexp, :expecting #"\\."}
{:tag :regexp, :expecting #"(:[^\p{L}_*{}\\]|[^:*{}\\])+"}],
:line 1,
:column 1,
:text nil}}
clojure.lang.Compiler$CompilerException: clojure.lang.ExceptionInfo: Parse error in route string {:failure Parse error at line 1, column 1:
nil
^
Expected one of:
#"(https?:)?//"
":"
"*"
#"\\."
#"(:[^\p{L}_*{}\\]|[^:*{}\\])+"
}, compiling:(/Users/laurent/dev/suricate/web/src/web/routes/api.clj:134:5)
I believe this is due to the way urls are parsed (with Clout) hence my question,
What's the correct way to match URLs without a trailing backspace in Compojure nested contexts?

Related

Does reader/read-string attach metadata to the forms

I've read somewhere that cljs.reader/read-string attaches metadata to the forms that it creates, like the position in the string read.
Is it true? Is it documented somewhere?
Thanks.
read-string doesn't add metadata to the returned form:
=> (meta (cljs.reader/read-string "(prn 0)"))
nil
Your compiled functions/defs/vars will have this type of metadata though:
=> (meta #'my-fn)
{:ns app.core,
:name my-fn,
:file "src/cljs/app/core.cljs",
:end-column 20,
:column 1,
:line 125,
:end-line 125,
:arglists ([{:keys [x]}]),
:doc nil,
:test nil}
I don't know about cljs.reader, but if you use clojure.tools.reader, you can do this. It's not particularly well documented, but you can see how by looking at the tests: https://github.com/clojure/tools.reader/blob/master/src/test/cljs/cljs/tools/metadata_test.cljs#L62-L70
In short, you have to pass the string to clojure.tools.reader.reader-types/indexing-push-back-reader, and from there to clojure.tools.reader/read. (In the test/example above, they first pass to reader-types/string-push-back-reader, but this doesn't appear to be strictly necessary).

Unable to use a regex in defroute

This routes works in Luminus/Compojure/Ring app
(GET "/page/:id" [id] (home-page id))
but this doesn't and throws an error:
(GET ["/page/:id" :id #"^[1-9]\d{0,2}$"] [id] (home-page id))
The error is "Page not found", even when I go to the same url "page/2"
Remove the anchors ^ $, which are redundant: apparently the keyword (:id) specifies which part of the route string (":id") is to be matched by the regex, in entirety.
The answer to why this is so is likely found in the implementation of this logic, Clout.

match a route in Compojure / Clout

I am trying to match routes of the following form : {{mongoID}}.{{width}}x{{height}}.{{extension}}
For instance, /5591499e2dbc18bd0f000050.240x240.jpegis a valid route.
I'd like to be able to destructure it like so :
{:id 5591499e2dbc18bd0f000050
:width 240
:height 240
:extension jpeg }
Compojure supports regex, and dots too apparently https://github.com/weavejester/compojure/issues/42 .
I can have individual regexes for each of the fields, but I'm not sure how to put that into the route path (I'm trying to use the array syntax) :
https://github.com/weavejester/compojure/wiki/Routes-In-Detail#matching-the-uri
Let's say I have this :
(GET ["/my-route/:mongoID.:widthx:height.:extension" :mongoID ...
:width ...
:height ...
:extension ...])
Obviously the string "/my-route/:mongoID.:widthx:height.:extension" won't work (just because the "x" is lost, maybe something else too).
How can I modify my route to make it match my arguments ?
Note : I'm also using Prismatic/Schema if that's useful.
Compojure uses clout for route matching. That's how it allows you to specify the regex for each parameter. The following works in clout:
user=> (require '[clout.core :as clout])
user=> (require '[ring.mock.request :refer [request]])
user=> (clout/route-matches (clout/route-compile "/my-route/:mongoID.:width{\\d+}x:height{\\d+}.:extension") (request :get "/my-route/5591499e2dbc18bd0f000050.240x240.jpeg"))
{:extension "jpeg", :height "240", :width "240", :mongoID "5591499e2dbc18bd0f000050"}
So the following should work in compojure:
(GET "/my-route/:mongoID.:width{\\d+}x:height{\\d+}.:extension"
[mongoID width height extension]
(do-something-with mongoID width heigth extension)

Using macros to generate Om components

I'm attempting to use macros to generate a series of similar Om components (e.g. a modal element which contains common boilerplate and a dynamic "body").
I've got the solution below mostly working. The one exception is accessing the appropriate owner in the form's on-submit event handler.
;; macros.clj
(defmacro build-modal
([disp-name body]
`(fn [_# owner#]
(reify
om.core/IRender
(~'render [_#]
(dom/div {:class "login-view-modal"}
(dom/div {:class "login-view-modal-backsplash"})
(dom/div {:class "login-view-modal-content"}
~#body))))) nil))
;; ui.cljs
(defn login-view-modal []
(bs-macros/build-modal
"login-modal"
'(dom/form {:on-submit (fn [event]
(.preventDefault event)
(let [username (get-value owner "username")
password (get-value owner "password")
data {:email_address username
:password password}]
(handle-login-view-modal-form-submit data)))}
;; elided for brevity
(dom/div
(dom/button "LOG IN")))))
(defcomponent home-page-view [app owner]
(init-state [_] {:text ""})
(render-state [this state]
;; elided for brevity
(dom/div (login-view-modal))))
The modal is rendered as expected, however when submitting the form I'm presented with the error: Uncaught TypeError: Cannot read property 'getDOMNode' of undefined, which seems to be because owner is not in scope. (Note, this element and its event handler functions as expected when constructed in full - i.e. without using a macro.)
How do I make the appropriate owner available to the body of the macro?
You need variable capture for this to work. Instead of gen-syming via owner# which will generate a unique symbol, just inline ~'owner wherever you want to refer to it.

Strange boolean behavior in Clojure with checkbox in form-data

I'm using an AngularJS resource to basically $.ajax() some form data.
Post.put({user:$scope.getLoggedInUser(), action:"new"},{body:$scope.postBody, "public":$scope.postPublic}, function(post) {
On the form is a checkbox named "public."
I am using this function to merge the form data with the params from the URL:
(defn get-params [request]
(merge (:params request) (parse-string (slurp (request :body)) true)))
when I println from my route's handler like so I get a nice true when the checkbox is checked:
(println (:public (get-params request)))
However, when I pass the parameter (i.e. not the println) to another function in my controller that talks to the database, I do another println at the beginning of that function and get nil instead.
I've tried passing it as
(read-string x)
(boolean (Boolean/valueOf x))
(Boolean/valueOf x)
to no avail.
One thing that might be causing it (but I don't know why) is that I'm wrapping the request through authentication like this:
(auth? request #(create x y z))
where the (create) function creates a record in the database.
I can't get it to be true no matter what I've tried.
EDIT: more complete code
CONTROLLER
(defn auth? [request callback-true & [callback-false]]
(println (callback-true))
(let [login-response (auth/login request)]
(if (and (not (string? login-response))
login-response)
(callback-true)
(if callback-false
(callback-false)
(json-response (str "{\"auth\":" login-response "}"), 401)))))
(defn create [user logged-in-user body public?]
(if (= logged-in-user user)
(json-response (post-view/single-post (post/create user body public?)))
(json-response "{\"auth\":\"can't create post under different logged in user\"}" 401)))
(defroutes routes
....
(PUT "/api/:user/new" request
(println request)
(auth? request
#(create (:user (request :params))
(:user (request :session))
(:body (get-params request))
(:public (get-params request)))))
....
)
MODEL
(defn create [username body public?]
(println public?)
(when-not (and
(str/blank? body)
(str/blank? username))
(let [user-id (:id (get-id-from-username username))
new-post-id
(:id
(sql/with-connection db
(sql/insert-values :post
[:usr_id :body :public] [user-id body (Boolean/valueOf public?)])))]
(by-id new-post-id))))
Don't call get-params twice. (request :body) returns a stream, which can't be read twice.