Modifying exception responses using reitit, returning json instead of plain/text

Hello, I am pretty new to clojure and its ecosystem so please don’t judge me harshly.

So I have been dabbling around in clojure for few weeks now. I wanted to build a typical rest api that would receive and respond in json. I am using reitit for routing and I have the below clj for building the ring handler.

(ns app 
  (:require [ring.middleware.json :refer [wrap-json-response wrap-json-params]])
  (:require [reitit.ring :as ring])
  (:require [cheshire.core :refer [generate-string]])
  (:require [reitit.ring.middleware.parameters :refer [parameters-middleware]])
  (:require [reitit.ring.middleware.exception :as exception]))

(def default-response-headers {"Content-Type" "application/json; charset=utf-8"})

(defn create-app [routes]
  (ring/ring-handler
   (ring/router
    routes
    {:data
     {:middleware [wrap-json-response 
                   exception/exception-middleware
                   wrap-json-params
                   parameters-middleware]}})
   (ring/routes
    (ring/redirect-trailing-slash-handler)
    (ring/create-default-handler
     {:not-found (constantly {:status 404 
                              :body (generate-string {:message "Route not found"})
                              :headers default-response-headers})
      :method-not-allowed (constantly {:status 405
                                       :body (generate-string {:message "Method not allowed"})
                                       :headers default-response-headers})
      :not-acceptable (constantly {:status 406 
                                   :body (generate-string {:message "Totally and utterly unacceptable"})
                                   :headers default-response-headers})}))))


When I send a malformed json e.g. ({“example”:}), I get this response body “Malformed JSON in request body.” as a text/plain. If I am correct, this is produced by wrap-json-params middleware. All I want to do is return something like this instead but I have been struggling to achieve that.

{"message": "Malformed JSON in request body."}

Any help would be greatly appreciated. Also, if you have some tips on how I could write better clojure code based on the above snippet, I’d like that very much.

I doubt much people are having the same requirements but if any beginners do. Fixing this was quite easy. Adding a new middleware like below (the middleware just detects if :body is string then wraps in a simple map)

(defn string->map-response-middleware [handler]
  (fn [request] (let [response (handler request) body (:body response)]
      (if (string? body)
        (assoc response :body {:message body})
        response))))

Then just add the middleware before converting the map into a JSON string.

Full snippet:

(ns app 
  (:require [ring.middleware.json :refer [wrap-json-response wrap-json-params]])
  (:require [reitit.ring :as ring])
  (:require [cheshire.core :refer [generate-string]])
  (:require [reitit.ring.middleware.parameters :refer [parameters-middleware]])
  (:require [reitit.ring.middleware.exception :as exception]))

(def default-response-headers {"Content-Type" "application/json; charset=utf-8"})

(defn string->map-response-middleware [handler]
  (fn [request] (let [response (handler request) body (:body response)]
      (if (string? body)
        (assoc response :body {:message body})
        response))))


(defn create-app [routes] 
  (ring/ring-handler
   (ring/router
    routes
    {:data {:middleware [wrap-json-response
                         string->map-response-middleware
                         exception/exception-middleware
                        ;;END OF RESPONSE MIDDLEWARES
                         wrap-json-params
                         parameters-middleware]}})
   (ring/routes
    (ring/redirect-trailing-slash-handler)
    (ring/create-default-handler
     {:not-found          (constantly {:status  404 
                                       :body    (generate-string {:message "Route not found"})
                                       :headers default-response-headers})
      :method-not-allowed (constantly {:status  405
                                       :body    (generate-string {:message "Method not allowed"})
                                       :headers default-response-headers})
      :not-acceptable     (constantly {:status  406 
                                       :body    (generate-string {:message "Totally and utterly unacceptable"})
                                       :headers default-response-headers})}))))

1 Like

I see you got a solution, but just so you know, you don’t need to repeat :require in your ns form. You can rewrite the form to look like this:

(ns app
  (:require [ring.middleware.json :refer [wrap-json-response wrap-json-params]]
            [reitit.ring :as ring]
            [cheshire.core :refer [generate-string]]
            [reitit.ring.middleware.parameters :refer [parameters-middleware]]
            [reitit.ring.middleware.exception :as exception]))
2 Likes

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.