How do I set default values for path parameters? - clojure

In the below example, how can I set a default value for the path parameter item-id?
(POST "/:id" [item-id]
:path-params [item-id :- Int]
:body [body Body]
:query-params [{item-name :- Str nil}]
:summary "Create or update a item."
(ok ...))

You should match the path-parameter name to the string placeholder. Path-params don't need defaults - if there is no path-parameter present, the route doesn't match. Here's a working example:
(require '[compojure.api.sweet :refer :all])
(require '[ring.util.http-response :refer :all])
(require '[schema.core :as s])
(require '[muuntaja.core :as m])
(def app
(api
(POST "/:item-id" []
:path-params [item-id :- s/Int]
:query-params [{item-name :- s/Str nil}]
:summary "Create or update a item."
(ok {:item-id item-id
:item-name item-name}))))
(->> {:request-method :post
:uri "/123"
:query-params {"item-name" "kikka"}}
(app)
:body
(m/decode m/instance "application/json"))
; => {:item-name "kikka", :item-id 123}

The default value is used if no value is present in the URL for the parameter. Path parameters are made optional by appending a question mark (?) to the end of the parameter name. For example, id?. The difference between optional values and default route parameters is:
A route parameter with a default value always produces a value.
An optional parameter has a value only when a value is provided by the request URL.
Path parameters may have constraints that must match the route value bound from the URL. Adding : and constraint name after the route parameter name

Related

Reagent atom value is stil nil after reset function

I made service endpoint api for getting single object by id and it works as expected. I tested it with Postman and in handler function. I use cljs-ajax library for asynchronous client. I cant change the state of Reagent atom when I get response. Here is the code:
(ns businesspartners.core
(:require [reagent.core :as r]
[ajax.core :refer [GET POST]]
[clojure.string :as string]))
(def business-partner (r/atom nil))
(defn get-partner-by-id [id]
(GET "/api/get-partner-by-id"
{:headers {"Accept" "application/transit+json"}
:params {:id id}
:handler #(reset! business-partner (:business-partner %))}))
When I tried to access business-partner atom I got nil value for that atom. I can't figure out why because another method is almost the same except it get's list of business partners and works fine.
When I change the get-partner-by-id function:
(defn get-partner-by-id [id]
(GET "/api/get-partner-by-id"
{:headers {"Accept" "application/transit+json"}
:params {:id id}
:handler (fn [arg]
(println :handler-arg arg)
(reset! business-partner (:business-partner arg))
(println "Business partner from handler: " #business-partner))}))
Output in the browser console:
:handler-arg {:_id 5e7ad2c84b5c2d44583e8ecd,
:address Main Street,
:email nenmit#gmail.com,
:phone 555888,
:name Nen Mit}
Business partner from handler: nil
So, as you can see, I have my object in handler as desired, but when I try to reset my atom nothing happens. That's the core of the problem I think. Thank you Alan.
When in doubt, use debug print statements. Make your handler look like this:
:handler (fn [arg]
(println :handler-arg arg)
(reset! business-partner (:business-partner arg)))
You may also want to use clojure.pprint/pprint to pretty-print the output, or also add (type arg) to the output.
You may also want to initialize the atom to a specific value like
:bp-default so you can see if the nil you observe is the original one or if it is being reset to nil.
Update
So it is clear the key :business-partner does not exist in the map you are receiveing. This is what you must debug.
Trying to pull a non-existent key out of a map always returns nil. You could also use the 3-arg version of get to make this explicit. Convert
(:business-partner arg) => (get arg :business-partner ::not-found)
and you'll see the keyword ::not-found appear in your atom, verifying what is occurring.
In order to catch these problems early, I nearly always use a simple function grab from the Tupelo library like so:
(:business-partner arg) => (grab :business-partner arg)
The grab function will throw an exception if the expected key is not found. This provides early-warning of problems so you can track them down faster.
Another hint: next time use prn instead of println and it will retain double-quotes on string output like:
"Main Street"

Map literal must contain an even number of forms

I'm trying to make an requiest to a web service which requires data in json and a secret (:key)
(ns fdsfdsfds.core
(:require [clj-http.client :as client])
(:require [clojure.data.json :as json]))
(defn -main [& args]
(client/post "https://fsdfdsfd.com/api/fdsfds"
{:body {(json/write-str {:key "fdsfdsfdsfd"})}}))
I'm having an error:
Exception in thread "main" java.lang.RuntimeException: Map literal must contain an even number of forms
There're the even number of them, though.
The problem is here:
{:body { (json/write-str {:key "fdsfdsfdsfd"}) }}
^-- single item missing value? --^
^----- this is a map too
There's nothing paired with the function call.
The :body has a map as a value, but you only have a function in there, not a possible key for its value, or if that is the key, there's no value for it.
You probably want to remove the outer map brackets and leave:
{:body (json/write-str {:key "fdsfdsfdsfd"})}
EDIT AFTER COMMENTS:
You're asking why the example on the site is using a map. Look carefully at the value being used, it's a string
(client/post "url://site.com/api"
{:basic-auth ["user" "pass"]
:body "{\"json\": \"input\"}"
;; ...
The map is made up of lines of key/value pairs. The first is
key = :basic-auth, value = ["user" "pass"]
The value here is an array.
The second line is:
key = :body, value = "any old string"
In this case the string is an escaped map, the same that would be returned from calling json/write-str

key delete! invoked when the method is PUT

I'm trying to build a simple rest api, using liberator and monger :
(ns tm.place.endpoint
(:require [liberator.core :refer [defresource]]
[monger [collection :as mc] [query :as mq]]
[clojure.data [json :as json]]
monger.json))
(defresource entity-place-resource [id]
:available-media-types ["application/json"]
:allowed-methods [:get :put :delete]
:exists? (if-let [place (mc/find-map-by-id "place" id)] {:place place})
:handle-ok :place
; :handle-created (fn [ctx]
; (let [body (-> ctx :request :body)]
; (assoc body :_id id)))
:handle-not-found []
:handle-not-acceptable "Should accept application/json"
:handle-method-not-allowed (json/write-str
{:message "Can only handle get, put and delete"})
; :handle-not-implemented
; :handle-no-content
:put! (fn [ctx]
(let [body (-> ctx :request :body)]
(mc/update-by-id "place" id (assoc body :_id id))))
:delete! (mc/remove-by-id "place" id)
:can-put-to-missing? false)
I'm using advanced rest client to see if it works. If the method is :get or :delete, it does perfectly what I want (It first check if a document is exists, and then do the appropriate action). However, if the method is :put, it simply spits out http 201 created, which I think the request is success, but the corresponding document is deleted, not updated.
If I comment out the :delete! line, the :put! is work as expected, so I'm guessing the culprit is the :delete! line, but I have no idea why because since I'm using :put method, I'm assuming :delete! should remain untouched. Any idea why?
As you have defined your delete! as a bare function it's going to get evaluated when you don't expect. The pattern you have followed for put! needs to be applied, where you return a fn that does the work rather than act directly.
http://clojure-liberator.github.io/liberator/faq.html
Due to the exact details of the defresource macro expansion, the form used as the value is evaluated at unexpected times.

In CLojure how to call xml-> with arbitrary preds

I want to create a function that allows me to pull contents from some feed, here's what I have... zf is from here
(:require
[clojure.zip :as z]
[clojure.data.zip.xml :only (attr text xml->)]
[clojure.xml :as xml ]
[clojure.contrib.zip-filter.xml :as zf]
)
(def data-url "http://api.eventful.com/rest/events/search?app_key=4H4Vff4PdrTGp3vV&keywords=music&location=Belgrade&date=Future")
(defn zipp [data] (z/xml-zip data))
(defn contents[cont & tags]
(assert (= (zf/xml-> (zipp(parsing cont)) (seq tags) text))))
but when I call it
(contents data-url :events :event :title)
I get an error
java.lang.RuntimeException: java.lang.ClassCastException: clojure.lang.ArraySeq cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)
(Updated in response to the comments: see end of answer for ready-made function parameterized by the tags to match.)
The following extracts the titles from the XML pointed at by the URL from the question text (tested at a Clojure 1.5.1 REPL with clojure.data.xml 0.0.7 and clojure.data.zip 0.1.1):
(require '[clojure.zip :as zip]
'[clojure.data.xml :as xml]
'[clojure.data.zip.xml :as xz]
'[clojure.java.io :as io])
(def data-url "http://api.eventful.com/rest/events/search?app_key=4H4Vff4PdrTGp3vV&keywords=music&location=Belgrade&date=Future")
(def data (-> data-url io/reader xml/parse))
(def z (zip/xml-zip data))
(mapcat (comp :content zip/node)
(xz/xml-> z
(xz/tag= :events)
(xz/tag= :event)
(xz/tag= :title)))
;; value of the above right now:
("Belgrade Early Music Festival, Gosta / Purcell: Dido & Aeneas"
"Belgrade Early Music Festival, Gosta / Purcell: Dido & Aeneas"
"Belgrade Early Music Festival, Gosta / Purcell: Dido & Aeneas"
"VIII Early Music Festival, Belgrade 2013"
"Kevlar Bikini"
"U-Recken - Tree of Life Pre event"
"Green Day"
"Smallman - Vrane Kamene (Crows Of Stone)"
"One Direction"
"One Direction in Serbia")
Some comments:
The clojure.contrib.* namespaces are all deprecated. xml-> now lives in clojure.data.zip.xml.
xml-> accepts a zip loc and a bunch of "predicates"; in this context, however, the word "predicate" has an unusual meaning of a filtering function working on zip locs. See clojure.data.zip.xml source for several functions which return such predicates; for an example of use, see above.
If you want to define a list of predicates separately, you can do that too, then use xml-> with apply:
(def loc-preds [(xz/tag= :events) (xz/tag= :event) (xz/tag= :title)])
(mapcat (comp :content zip/node) (apply xz/xml-> z loc-preds))
;; value returned as above
Update: Here's a function which takes the url and keywords naming tags as arguments and returns the content found at the tags:
(defn get-content-from-tags [url & tags]
(mapcat (comp :content zip/node)
(apply xz/xml->
(-> url io/reader xml/parse zip/xml-zip)
(for [t tags]
(xz/tag= t)))))
Calling it like so:
(get-content-from-tags data-url :events :event :title)
gives the same result as the mapcat form above.

How to get JSON post data in Noir

A while back, Chris Granger posted this middleware to get JSON hashes to appear in the defpage params under an umbrella "backbone" element.
(defn backbone [handler]
(fn [req]
(let [neue (if (= "application/json" (get-in req [:headers "content-type"]))
(update-in req [:params] assoc :backbone (json/parse-string (slurp (:body req)) true))
req)]
(handler neue))))
How could I modify this code to have the JSON elements appear as top-level params in defpage; i.e. get rid of the :backbone umbrella?
There are two things you can do. One option is to replace the value of :params with the map returned after parsing the JSON. In order to do that, just associate the new map to the :params key.
(assoc req [:params] (json/parse-string (slurp (:body req)) true))
The other option (as suggested by #dAni) is to merge the values of the parsed JSON into so that existing values in the :params map are not overridden. The reason why you need to use partial instead of just using merge here is because the final map is the merged result of the maps from left-to-right. Your solution works if you want the values from the JSON map to take precedence.
(update-in req [:params]
(partial merge (json/parse-string (slurp (:body req)) true)))
Got it. assoc just works for one element so you have to put everything under the :backbone umbrella. To push all the JSON elements into the params, you have to use merge. So change the 4th line to:
(update-in req [:params] merge (json/parse-string (slurp (:body req)) true))
If you don't mind pulling in another dependency, you can use the ring-middleware-format library.
Instructions:
Add [ring-middleware-format "0.1.1"] to your project.clj
and then in your server.clj, add the following code:
Code:
(:require [ring.middleware.format-params :as format-params])
(server/add-middleware format-params/wrap-json-params)
(defn -main [& m]
; Start the server...
)
Now any incoming JSON will be available to use just like form POSTdata.