Multiple clojure-liberator decisions reads request body - clojure

I have one defresource, that is supposed to take POST-requests, validate request body in :malformed-decision, save the body to database in :post!-decision and return the saved body in :handle-created.
(defn parse-project [context] (json/read-str
(slurp (get-in context [:request :body]))
:key-fn keyword))
(defresource add-new-project
:malformed? (fn[ctx] (not (project-is-valid (parse-project ctx))))
:handle-malformed (fn [_] (generate-string (str "Malformed json!")))
...
:post! (fn [ctx] (save-to-db (parse-project ctx))
:handle-created (fn [ctx] (... parse-project ...))
So my code reads three times the ByteArrayInputStream(that comes from :request :body) with slurp-function. First time works but second time when slurp is called, nil is passed as a param and comes java.io.EOFException: JSON error. I think the reader starts reading where it was left last time.
How could I read the body of the request three times? Or is there nice way to save the result of reading to variable and pass that to other liberator-decisions?

The context can be updated by the outcome of each decision and action functions. You can parse the project once in malformed? and return a map with the parsed project which will be merged into the context so it is available to the following decisions and actions. For example:
(defresource add-new-project
:malformed? (fn[ctx] (let [project (parse-project ctx)]
(when (project-is-valid project)
{:project project})))
:handle-malformed (fn [_] (generate-string (str "Malformed json!")))
:post! (fn [ctx] (save-to-db (:project ctx)))
:handle-created (fn [ctx] (do-something (:project ctx))))
If the project is valid, :malformed? returns the {:project project} map which will be merged into the context to be used in the next decisions and actions.
If the project is not valid, it will return nil so execution continues in :handle-malformed.
For more info on liberator's execution model, see https://clojure-liberator.github.io/liberator/doc/execution-model.html

Related

Testing with core.async - flushing go blocks inside a test?

I am trying to write a test like this:
(deftest login-form-rendering
(async done (with-redefs [http/post fake-login-success]
(with-mounted-component (c/login-form login!)
(fn [c div]
(.click (.getElementById js/document "Login"))
(is (= (:page #app-state) :location)))))
(done)))
I have this mock:
(defn fake-login-success [& _]
(let [ch (chan)
response {:status 200}]
(go (>! ch response)
ch)))
The login function does this:
(defn login! [username password]
(go (let [result (->> {:form-params {:username #username :password #password}}
(http/post "/api/login")
(<!))
{status :status body :body} result]
(case status
401 (swap! app-state assoc :page :bad-login)
200 (swap! app-state assoc :page :location)))))
The login form is a reagent component that takes a callback for onClick. app-state is a globally accessible atom.
The problem I'm facing is that the go block inside login! is never executed. Is there something I need to do to flush the channel or something?
I see there is also this unanswered question that's similar: Testing core.async code in ClojureScript. One difference seems to be that I don't have an explicit channel in my code under test. That's being generated by the cljs-http post.
I believe your problem is a misplaced parenthesis.
(let [ch (chan)
response {:status 200}]
(go (>! ch response)
ch))
The return value of this let is the go block's channel. It is a channel containing nothing, since:
The go block is trying to put on an unbuffered channel, requiring some other operation to "meet" it and do the complementary take in order for its own execution to proceed.
ch is lexically hidden from the outside world, so no operation can exist which can perform the complementary take.
If something somehow did take from it (or we added a buffer to ch so the first put succeeds), ch would be put on the returned channel.
(let [ch (chan)
response {:status 200}]
(go (>! ch response))
ch)
The return value of this let is ch, with no additional channel wrapping it. The go-block is parked trying to put on ch until something like your test gets around to taking from it.
However, since we're ultimately just looking to construct a channel with a constant on it, go itself seems like the simplest solution:
(defn fake-login-success [& _]
(go {:status 200})

Set initial state for missing key in component data

I am trying to get into ClojureScript and Om. There is a specific case which has me running in circles.
I have a component that is first rendered without a key.
(defn model-view [data owner]
(reify
om/IWillMount
(will-mount [_]
(om/transact! data [:stats] (fn [] {}))
(go
(let [response ((<! (api/get-stats (data :id))) :body)
stats (:stats response)]
(om/update! data [:stats] stats))))
om/IRender
(render [_]
(dom/div nil
(dom/h3 nil (data :title))
;; Here I want to use the :stats key in data that I
;; queried for in IWillMount, but its not present
;; the first time this model is rendered. It's only present
;; AFTER IWillMount has ran.
(om/build model-stats-view (data :stats)))))
The first time this component is called, the :stats key is simply not present in data. That's why I do an API call to get its stats. But React still calls the render function, thus the component crashes.
How can I set an initial state in this component that gives data an empty map called :stats, thus preventing trying to render nil in the (om/build model-stats-view) call?
I prefer to do all of my initialization in init-state, then access it in render-state. And I put a go-loop in my did-mount. When you update your init-state (i.e. :e-map) in the go-loop, it forces a call to render/re-render of the component. I use this in all of my components for inter-component/intra-component messaging. Just push something into a pub/sub channel and we are off to the races.
;To update my state I use a function:
(defn set-owner-state! [owner old-map-key old-map new-map]
(om/set-state! owner {old-map-key (merge old-map new-map)}))
om/IInitState
(init-state [_]
(println "queue->init-state")
{:e-map {:active-fsm nil}})
om/IDidMount
(did-mount [_]
(go-loop []
(let [[v _] (alts! [server-fsm-events dispatcher-events])
current-state (om/get-state owner)
e-map (:e-map current-state)]
; what goes in here is the logic to respond to a message
; in my case I have a match, it could be a cond or a set of
; if's.
(set-owner-state! owner :e-map e-map {:active-fsm :active :task-paths nil})
...
om/IRenderState
(render-state [_ {:keys [e-map]}]
(println "e-map:" e-map)
...

get agent content and convert it to JSON

I'm still newbie in clojure and I'm trying to build application which read two files and write the diffrence on JSON file
(defn read-csv
"reads data."
[]
(with-open [rdr (
io/reader "resources/staples_data.csv")]
(doseq [line (rest(line-seq rdr))]
(println(vec(re-seq #"[^,]+" line))))))
(defn read-psv
"reads data."
[]
(with-open [rdr (
io/reader "resources/external_data.psv")]
(doseq [line (rest(line-seq rdr))]
; (print(vec(re-seq #"[^|]+" line))))))
(doall(vec(re-seq #"[^|]+" line))))))
(defn process-content []
(let [csv-records (agent read-csv)
psv-records (agent read-psv)]
(json/write-str {"my-data" #csv-records "other-data" #psv-records}))
)
Im getting an exception: Exception Don't know how to write JSON of class $read_csv clojure.data.json/write-generic (json.clj:385)
Please some help with some explanation, thanks in advance!
You are giving the agent a function as its initial value. Perhaps you meant to do an asynchronous call to that function instead? In that case, a future is a better match for your scenario as shown. agent is synchronous, it's send and send-off that are async, and they assume you are propagating some state across calls which doesn't match your usage here.
(defn process-content []
(let [csv-records (future-call read-csv)
psv-records (future-call read-psv)]
(json/write-str {"my-data" #csv-records "other-data" #psv-records})))
The problem after that is that doseq is only for side effects, and always returns nil. If you want the results read from the csv files (evaluating eagerly so you are still in the scope of the with-open call), use (doall (for ...)) as a replacement for (doseq ...). Also, the println in read-csv will need to be removed, or replaced with (doto (vec (re-seq #"[^,]+" line)) println) because println always returns nil, and I assume you want the actual data from the file, not a list of nils.

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.

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.