Compojure-specific destructuring and query strings - clojure

I'm trying to access the parameter foo, using compojure, in a request like this:
/api/xyz?foo=bar
The compojure destructuring syntax looks good, so I would like to use it. However the following just serves me the "Page not found":
(defroutes app-routes
(GET "/api/xyz/:foo" [foo] (str "foo: " foo))
(route/not-found "Page not found"))
Which is kind of weird, since the verbose destructuring below works and gives me "foo: bar":
(defroutes app-routes
(GET "/api/xyz" {{foo :foo} :params} (str "foo: " foo))
(route/not-found "Page not found"))
What am I missing?

If foo is always passed as a URL parameter, you want your code to be like this:
(defroutes app-routes
(GET "/api/xyz" [foo] (str "foo: " foo))
(route/not-found "Page not found"))

Related

calling special form `set!` in a clojure macro

Given Java instance obj and a member name (string) "Foo", and a map conf, I am trying to generate Clojure code that will look like this:
(if (get conf "Foo")
(set! (.Foo obj) (get conf "foo")
obj)
And also, if I know that "SomeEnum" is a Java enum name, a code like this:
(if (get conf "SomeEnum")
(set! (.someEnum obj)(Enum/valueOf SomeEnum (get conf "SomeEnum")))
obj)
Here is what I came up with:
(defmacro set-java [obj conf obj-name]
`(if (get ~conf ~obj-name)
(set! (. ~obj ~(symbol obj-name)) (get ~conf ~obj-name))
~obj))
(defn lowercase-first [s]
(apply str (Character/toLowerCase (first s)) (rest s)))
(defmacro set-java-enum [obj conf obj-name]
`(if (get ~conf ~obj-name)
(set! (. ~obj ~(symbol (lowercase-first obj-name)))
(Enum/valueOf ~(symbol obj-name) (get ~conf ~obj-name)))
~obj))
Testing with macroexpand seems to give the right result, but after trying:
(defn ^Policy map->policy [conf]
(-> (Policy.)
(set-java-enum conf "CommitLevel")
(set-java conf "durableDelete")
(set-java conf "expiration")
(set-java conf "generation")
(set-java-enum conf "GenerationPolicy")
(set-java-enum conf "RecordExistsAction")
(set-java conf "respondAllOps")))
I got a weird endless loop of reflection warnings.
--- edit ---
after battelling with that for quite a while, I gave up threading (->) and ended with:
(defmacro set-java [obj conf obj-name]
`(when (get ~conf ~obj-name)
(set! (. ~obj ~(symbol obj-name)) (get ~conf ~obj-name))))
(defn lowercase-first [s]
(apply str (Character/toLowerCase ^Character (first s)) (rest s)))
(defmacro set-java-enum [obj conf obj-name]
`(when (get ~conf ~obj-name)
(set! (. ~obj ~(symbol (lowercase-first obj-name)))
(Enum/valueOf ~(symbol obj-name) (get ~conf ~obj-name)))))
(defn map->write-policy [conf]
(let [wp (WritePolicy. (map->policy conf))]
(set-java-enum wp conf "CommitLevel")
(set-java wp conf "durableDelete")
;; more non threaded object manipulation
wp))
So I am still not sure what was the reflection warning endless loop about, but this is hopefully handful as well, and can be improved further.
I think this is due to your interpolating ~obj multiple times in each macro. Is your "loop" actually endless, or is it just ~128 or ~256 steps long?
In any case, the fix for that particular issue (whether or not it's the root cause of the problem you describe) is to wrap the form in (let [obj# ~obj] ...) and then refer to obj# below, so the argument is only interpolated once.
You can (should!) do this with conf and obj-name too, but they're probably not actively causing problems, at least with the usage code you provided.

Clojure multimethod giving unexpected null pointer

I'm having a hard time getting the multimethods in Clojure to work as I would expect. A distillation of my code is as follows.
(defn commandType [_ command] (:command-type command))
(defmulti testMulti commandType)
(defmethod testMulti :one [game command] (str "blah"))
(defmethod testMulti :default [& args] "Cannot understand")
(testMulti "something" {:command-type :one})
(commandType "something" {:command-type :one})
Now I would expect here to have the method commandType called on the arguments which would of course return :one which should send it to the first defmethod but instead I get a null pointer exception. Even the simplest invocation of a multimethod I could come up with gives me a null pointer:
(defmulti simpleMulti :key)
(defmethod simpleMulti "basic" [params] "basic value")
(simpleMulti {:key "basic"})
And yet the example in the clojure docs located here works fine. Is there something fundamental I'm doing wrong?
So far as I can see, it works.
Given
(defmulti testMulti (fn [_ command] (:command-type command)))
(defmethod testMulti :one [game command] (str "blah"))
(defmethod testMulti :default [& args] "Cannot understand")
then
(testMulti "something" {:command-type :one})
; "blah"
(testMulti "something" {:command-type :two})
; "Cannot understand"
(testMulti "something" 5)
; "Cannot understand"
as expected.
I reset the REPL before running the above afresh.
And the simple example works too. Given
(defmulti simpleMulti :key)
(defmethod simpleMulti "basic" [params] "basic value")
then
(simpleMulti {:key "basic"})
; "basic value"

How do I handle some but not all http methods in compojure with Liberator?

I'm using liberator with compojure, and wanted to send multiple methods (but not all methods) to the save resource. Rather than repeating myself, I'd like to have something that defines multiple handlers at once.
An example:
(defroutes abc
(GET "/x" [] my-func)
(HEAD "/x" [] my-func)
(OPTIONS "/x" [] my-func))
Should be closer to:
(defroutes abc
(GET-HEAD-OPTIONS "/x" [] my-func))
As shown in the tutorial the idiomatic way is to use the ANY key on the route and then define the :allowed-methods [:get :head :options] on your resource. You will need to implement the :handle-ok and :handle-options
(defroute collection-example
(ANY ["/collection/:id" #".*"] [id] (entry-resource id))
(ANY "/collection" [] list-resource))
(defresource list-resource
:available-media-types ["application/json"]
:allowed-methods [:get :post]
:known-content-type? #(check-content-type % ["application/json"])
:malformed? #(parse-json % ::data)
:handle-ok #(map (fn [id] (str (build-entry-url (get % :request) id)))
(keys #entries)))
After several false starts, I realized that the compojure.core/context macro can be used for this purpose. I defined the following macro:
(defmacro read-only "Generate a route that matches HEAD, GET, or OPTIONS"
[path args & body]
`(context "" []
(GET ~path ~args ~#body)
(HEAD ~path ~args ~#body)
(OPTIONS ~path ~args ~#body)))
Which will let you do:
(read-only "/x" [] my-func)
And seems to do what I need.

Parsing values from multiple checkboxes using compojure

I have created small compojure web application, which can display multiple values, fetched from other website, using provided URL. At the moment, this URL is hard coded in one of my functions, and now I would like to add feature for dynamical URL creation, based upon values in text field and checkbox.
This is how my page looks like:
(defn view-layout [& content]
(html [:body content]))
(defn view-input []
(view-layout
[:h2 "Find"]
[:form {:method "post" :action "/"}
( for [category ["Cat1" "Cat2" "Cat3"]]
[:input {:type "checkbox" :id category } category ] )
[:br]
[:input {:type "text" :id "a" :value "insert manga name"}] [:br]
[:input.action {:type "submit" :value "Find"}]
[:a {:href "/downloads"} "Downloads"]]))
(defn view-output []
(view-layout
[:h2 "default images"]
[:form {:method "post" :action "/"}
(for [name (get-content-from-url (create-url))]
[:label name [:br]]
)]))
(defn create-manga-url
[]
"http://www.mysite.net/search/?tfield=&check=000")
Here are the routes:
(defroutes main-routes
(GET "/" []
(view-input))
(GET "/downloads" []
(view-downloads))
(POST "/" []
(view-output) ))
At the moment, I need help with (create-url) function (returns a string), where I would like to fetch all fields, mandatory for my search (one text field and 3 checkboxe's) , and parse values from them, which will be fed into (concatenated) the URL - for checkbox, if checked, the check section will have value 1, instead of 0, or remain 0 if not (check=100, or 010, 011 if two check boxes were selected) . In case of text field, the tfield=userinputtext.
EDIT
I spent a lot of time as a .Net and Java developer, and this part of compojure is a total mystery for me.
This is what I would like to achieve with (create-url) function (pseudo code written in OO style):
(defn create-url [*text_field cbox1 cbox2 cbox3*]
(def url "http://www.mysite.net/search/?")
(def tfield "tfield=")
(def cbox "&check=")
(if (checked? cbox1)
(str cbox "1")
(str cbox "0"))
(if (checked? cbox2)
(str cbox "1")
(str cbox "0"))
(if (checked? cbox3)
(str cbox "1")
(str cbox "0"))
(str tfield (:value text_field))
(str url tbox cbox))
I apologize for how this pseudo code looks like, but this is the part that I would like to learn: How can I scoop data from form, and parse it (in this case i would like to attachh values from form fields into the string)
Can anyone help me with this?
First, you need to add 'name' attributes to your HTML input elements. The 'id' attributes aren't sent to the server on post.
Next, I guess a quick way of doing this similar to your example is:
(POST "/" [a Cat1 Cat2 Cat3] (create-url a [Cat1 Cat2 Cat3]))
and then something like this:
(defn checked? [c]
(and c (= c "on")))
(defn checked->num [c]
(if (checked? c) "1" "0"))
(defn create-url [a cats]
(str "x?tfield=" a "&check="
(apply str (for [c cats] (checked->num c)))))
Or just drop the two helpers:
(defn create-url [a cats]
(str "x?tfield=" a "&check="
(apply str (map #(if (= "on" %) "1" "0") cats))))

URL Checker in Clojure?

I have a URL checker that I use in Perl. I was wondering how something like this would be done in Clojure. I have a file with thousands of URLs and I'd like the output file to contain the URL (minus http://, https://) and a simple :1 for valid and :0 for false. Ideally, I could check each site concurrently, considering that this is one of Clojure's strengths.
Input
http://www.google.com
http://www.cnn.com
http://www.msnbc.com
http://www.abadurlisnotgood.com
Output
www.google.com:1
www.cnn.com:1
www.msnbc.com:1
www.abadurlisnotgood.com:0
I assume by "valid URL" you mean HTTP response 200. This might work. It requires clojure-contrib. Change map to pmap to attempt to make it parallel, like Arthur Ulfeldt mentioned.
(use '(clojure.contrib duck-streams
java-utils
str-utils))
(import '(java.net URL
URLConnection
HttpURLConnection
UnknownHostException))
(defn check-url [url]
(str (re-sub #"^(?i)http:/+" "" url)
":"
(try
(let [c (cast HttpURLConnection
(.openConnection (URL. url)))]
(if (= 200 (.getResponseCode c))
1
0))
(catch UnknownHostException _
0))))
(defn check-urls-from-file [filename]
(doseq [line (map check-url
(read-lines (as-file filename)))]
(println line)))
Given your example as input:
user> (check-urls-from-file "urls.txt")
www.google.com:1
www.cnn.com:1
www.msnbc.com:1
www.abadurlisnotgood.com:0
Write a small function that appends a ":1" or ":0" to a url and then use pmap to apply it in parallel to all the urls.
(defn check-a-url [url] .... )
(pmap #(if (check-a-url %) (str url ":1") (str url ":0")))
Clojure now has a as-url function in clojure.java.io:
(as-url "http://google.com") ;;=> #object[java.net.URL 0x5dedf9bd "http://google.com"]
(str (as-url "http://google.com")) ;;=> "http://google.com"
(as-url "notanurl") ;; java.net.MalformedURLException
Based on that we could write a function like so:
(defn check-url
"checks if the url is well formed"
[url]
(str (clojure.string/replace-first url #"(http://|https://)" "")
":"
(try (as-url url) ;; built-in, does not perform an actual request, and does very little validation
1
(catch Exception e 0))))
(defn check-urls-from-file
"from Brian Carper answer"
[filename]
(doseq [line (map check-url (read-lines (as-file filename)))]
(println line)))
Instead of pmap, I used agents with send-off in conjunction with the above solution. I think this is better when there is blocking I/O. I believe pmap has limited concurrency too. Here's what I have so far. I wonder how this will scale with thousands of URLs.
(use '(clojure.contrib duck-streams
java-utils
str-utils))
(import '(java.net URL
URLConnection
HttpURLConnection
UnknownHostException))
(defn check-url [url]
(str (re-sub #"^(?i)http:/+" "" url)
":"
(try
(let [c (cast HttpURLConnection
(.openConnection (URL. url)))]
(if (= 200 (.getResponseCode c))
1
0))
(catch UnknownHostException _
0))))
(def urls (read-lines "urls.txt"))
(def agents (for [url urls] (agent url)))
(doseq [agent agents]
(send-off agent check-url))
(apply await agents)
(def x '())
(doseq [url (filter deref agents)]
(def x (cons #url x)))
(prn x)
(shutdown-agents)