1
votes

I'm two days into learning Clojure by writing a simple REST server using ring-clojure and Compojure with ring-json's wrap-json-body middleware.

So far, I have:

A vector users containing the users (with a couple of default users):

(def users [{:id 0 :username "aname"}
            {:id 1 :username "anothername"}])

A function (form?) save-user that accepts a map (user) and looks for existing users with the same username. If the username is available, I overwrite the users vector to include the new user before returning HTTP 201. If the username is taken, I simply return HTTP 400:

(defn save-user [user]
  (prn users)
  (if
    (not-any? #(= (:username %) (:username user)) users)
    (fn [request]
      (def users (conj users user))
      (status
        (response (str "Saved user with username: " (:username user)))
        201))
    (status
      (response (str "User with username '" (:username user) "' already exists"))
      400)))

A route for POST /users which calls save-user with the received map:

(defroutes app-routes
       (POST "/users" request (save-user (:body request))))

I don't think it matters, but the middleware is applied like this:

(def app
  (-> app-routes
      (wrap-cors :access-control-allow-origin "*" :access-control-allow-methods "*")
      (wrap-json-response)
      (wrap-keyword-params)
      (wrap-json-body {:keywords? true :bigdecimals? true})
      (wrap-defaults (assoc site-defaults :security false))))

My problem:

For whatever reason, the entire request map is passed to the function i pass as then inside the if. Printing it:

  (if
    (not-any? #(= (:username %) (:username user)) users)
    (fn [request]
      (prn request))
    ...

... gives me this:

{:ssl-client-cert nil, :cookies {}, :remote-addr "0:0:0:0:0:0:0:1", :params {}, :flash nil, :route-params {}, :headers {"host" "localhost:3000", "accept" "*/*", "content-length" "42", "content-type" "application/json", "user-agent" "curl/7.43.0"}, :server-port 3000, :content-length 42, :form-params {}, :compojure/route [:post "/users"], :session/key nil, :query-params {}, :content-type "application/json", :character-encoding nil, :uri "/users", :server-name "localhost", :query-string nil, :body {:username "testusername", :password "testpassword"}, :multipart-params {}, :scheme :http, :request-method :post, :session {}}

The same happens if I pass an anonymous function as the if's else. However, nothing wrong happens when I only pass (status ...), like in the code above.

From what I understand, the request map shouldn't be available inside save-user at all, since it's not passed as an argument. Why is it passed to my anonymous function, and is there any way to simply ignore it?

1
Not sure what the question is. Why are you returning a function from the save-user function at all? You could just return the response directly (replace (fn [request] with (do).glts
By the way, you should wrap the users vector in an atom and then operate on the atom. Redefining global vars from inside a function is a big no-no in Clojure.glts
@glts I'm not returning a function from save-user, but I'm passing one to the if because I need to both save the user and return the HTTP response object (returned from the status function). As for wrapping the list in an atom: thanks! That was because I'm a newbie at this.Martin Lehmann
Hi Martin, I’ve converted my comment into an answer. You are returning a function from the if: in Clojure each conditional branch is an expression that returns a value.glts

1 Answers

3
votes

In save-user you are returning a function that takes a request and returns a response (a handler).

I suppose you should instead just return the response directly. Just replace (fn [request] with (do to wrap multiple expressions in a single form.

(def users (atom [{:id 0 :username "aname"}
                  {:id 1 :username "anothername"}]))

(defn save-user [user]
  (if (not-any? #(= (:username %) (:username user)) @users)
    (do
      (swap! users conj user)
      (status
        (response (str "Saved user with username: " (:username user)))
        201))
    (status
      (response (str "User with username '" (:username user) "' already exists"))
      400)))

I’ve also changed the global users var to be an atom. Redefining global vars from inside a function is a big no-no in Clojure.