Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return json data from post! handler in clojure liberator?

How to return json-data with Clojure Liberator? This code doesn't work:

(defresource poster []
         :allowed-methods [:post :options]
         :available-media-types ["application/json"]
         :post!      (fn [ctx] (println "posting..."))
         :handle-created (fn [ctx] {:created "ok"}))

Should handle-created be called after post?

like image 901
Curiosity Avatar asked Jan 23 '15 10:01

Curiosity


2 Answers

The function associated with the :post! key is not a handler function. The Liberator documentation calls it an action function.

Action functions The keys :post!, :put! and :delete! provide points that are suited well to enact side effects. While they are evaluated like decision functions, the boolean value has no effect and the next decision step is constant. The context update works exactly like for decision functions.

So you can't generate an http response directly from the function associated with :post!.

The function associated with the :post! key could return something and that something would get merged into the context.

The context update works exactly like for decision functions.

Then a handler function could later pull that something out of the context and use it to form an http response. It is possible that any one of the handler functions associated with these keys could execute subsequently: :handle-ok, :handle-created, :handle-no-content, :handle-see-other, :handle-multiple-representations

This decision graph determines which handler will execute.

Its probably best to just respond with a location header pointing to your newly created resource and no body, but here is one example of creating a response with a JSON body and a 201 status.

(POST "/foo" [] (resource :allowed-methods [:post]
                           :available-media-types ["application/json"]
                           :handle-created {:created "ok"}))

Trying it out:

curl -X POST "localhost/foo" 
{"created":"ok"}

You can see which JSON library Liberator uses in its project.clj file. If you want to generate JSON strings yourself you can do it like this:

:handle-created (fn [ctx] (liberator.representation/ring-response 
                          {:status 201 :body "{created:\"ok\"}"}))

It is mentioned here

like image 169
kevincasey Avatar answered Dec 31 '22 15:12

kevincasey


This is the way I do it - it seems to work, but I've only just started using Liberator, so there might be better or more correct ways of doing this!

I think what you need is a handle-created handler. For example -

(defresource shopping-calc
  :allowed-methods [:post]
  :available-media-types ["application/json"]
  :malformed? (fn [context]
                (let [params (get-in context [:request :params])]
                  (or (empty? params)
                      (is-malformed? params))))
  :handle-malformed (fn [context]
                      (let [params (get-in context [:request :params])]
                        (generate-string (handle-malformed-calc params))))
  :handle-created (fn [context]
                    (let [params (get-in context [:request :params])]
                      (generate-string (calculate-total params)))))

and I have a handler-created handler like this

(defn calculate-total [params]
  {:total (calc params)})

I also use the ring/json middleware and in my dev environment, add the liberator trace facility. The Liberator trace facility is very handy as it will add headers to the response which show the decision points for Liberator. So, for you problem, it probably would have shown that Liberator was using the default handle-created handler, which just returns the headers. To return your own json, you need to define the handler.

Note that I'm not using the post! method. This is because in this example, I'm not actually creating some sort of new object/item, such as adding a record to some sort of store. If you were doing this, what you might do would be to use post! to add the record and have handle-created defined to then get the new record (possibly with other new fields, such as a record id or timestamp etc) and return it.

I use the :malformed? and handle-malformed to do basic error checking. If :malformed? returns true, the handle-malformed header is called, which returns an error status and an error message in the json body. I find it helps a lot to have your errors also returned in json so that you can handle everything consistently on the client end.

My handlers and middleware definitions are below. Note that as I'm serving up both application and api routes, they are a little more complicated because I want some middleware applied to some routes and other middleware applied to others. There is also a minor bug in ring/ring-defaults which, once fixed, will modify things as currently I cannot use the wrap-defaults site-api middleware. Note the wrap-trace middleware.

(def app
  (if (env :dev)
    (routes (-> api-routes
                (wrap-reload)
                (wrap-routes wrap-json-params)
                (wrap-routes wrap-defaults api-defaults)
                (wrap-routes wrap-trace :header :ui))
            (-> app-routes
                (wrap-routes wrap-error-page)
                (wrap-routes wrap-exceptions)))
    (routes (-> api-routes
                (wrap-routes wrap-json-params)
                (wrap-routes wrap-defaults api-defaults))
            app-routes)))
like image 35
Tim X Avatar answered Dec 31 '22 13:12

Tim X