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

3

6

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?

Strontia answered 23/1, 2015 at 10:8 Comment(6)
Looking at the Liberator decision graph, maybe adding :new? true to your resource might help? (If you really want to return 201 Created, that is.)Snowslide
It returns 201 without json data. I would like to return json data with 201 header. Is it possible?Strontia
I also just noted you have :available-media-types defined as a string - it should be a vector of strings representing available media types i.e. in your case :available-media-types ["application/json"].Gothicism
Yes, thanks, it was a typoStrontia
how do I return 200 OK instead of 201 Created? I'm trying to post a text file on an endpoint and default response is 201 Created, not 200 OK..What am i missing ?Exact
@DanielColceag take a look at this: clojure-liberator.github.io/liberator/tutorial/… I think that you are probably getting true on new?.Xerography
R
3

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

Revert answered 20/7, 2016 at 23:36 Comment(0)
G
1

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)))
Gothicism answered 23/1, 2015 at 23:26 Comment(1)
Thank you. I spent some time studying liberator's graph yesterday and it's working now with post! and handle-created. Maybe I was too tired yesterday. I've also implemented returning from delete! method adding respond-with-entity? with true and handling handle-ok as a result of delete operation.Strontia
H
1

The code 201 created is used to return links to the newly created resource in the body along with a Location header. If you would like to return the newly created resource in the body, you should use 200 ok. By default, Liberator will end up at 204 no-content after POST. You would need to set :respond-with-entity? true.

You resource definition would look like:

(defresource poster []
   :allowed-methods [:post :options]
   :available-media-types ["application/json"]
   :malformed? (fn [ctx]
                 [false {::resource (parse-json (get-in ctx [:request :body]))}])
   :post! (fn [ctx]
            (persist (::resource ctx)))
   :handle-ok (fn [ctx]
                (generate-json (::resource ctx))))

I recommend Ceshire to parse and generate JSON.

Head answered 24/1, 2015 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.