Search code examples
clojurecompojurering

Request map passed to my anonymous function inside a Compojure handler


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?


Solution

  • 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.