Search code examples
clojurering

Cannot figure out how (status/forbidden!) works


I can read the Ring docs and know that (status/forbidden!) has the effect of throwing an exception which returns HTTP 403.

(ns com.example.mosaic.endpoint.v2.api
  (:require
    ; ...
    [ring.util.http-response :as status]
    [ring.util.response :as response])
  (:import [org.joda.time DateTime]))

(defn- check-error-response!
  "Translate the response from boundary server into an appropriate http error response."
  [response user-id field-id]
  (let [{:keys [status]} response]
    (cond
      (contains? #{401 403 404} status) (status/forbidden! {:error-code "BS-004" :user-id user-id :field-id field-id })
      (= 500 status) (status/internal-server-error! {:error-code "BS-005" :user-id user-id :field-id field-id }))))

The problem is, I don't understand HOW it does that. I look in the ring code and can't find a def for (status/forbidden!) anywhere. I do see a...

(defstatus Forbidden 403 "Forbidden" "The request was a legal request but the server is refusing to respond to it.")

...in the https://github.com/metosin/ring-http-response/blob/0.5.1/src/ring/util/http_response.clj, but there is no '!' on it, and Forbidden is not the same as forbidden in any event. What sorcery is involved in allowing this code to compile and work? Please explain.

The project.clj :dependencies are

:dependencies
  [[org.clojure/clojure "1.6.0"]
   [org.clojure/tools.cli "0.3.1"]
   [org.clojure/tools.logging "0.3.0"]
   [ring/ring-core "1.3.0"]
   [ring/ring-jetty-adapter "1.3.0"]
   [metosin/ring-swagger-ui "2.0.24"]
   [metosin/ring-swagger "0.14.0"]
   [metosin/compojure-api "0.15.1"]
   [org.slf4j/slf4j-api "1.6.2"]
   [org.slf4j/slf4j-log4j12 "1.6.2"]
   [bk/ring-gzip "0.1.1"]
   [com.example/field-layer "2015.04.17T16.17.30.874cd09"]
   [com.cemerick/drawbridge "0.0.6"]
   [clj-http "1.1.0"]
   [com.example.the-request/the-clj-http "2015.04.15T00.53.11.843c71c"]
   [org.apache.httpcomponents/httpclient "4.3.6"]
   [com.example/compojure-api-utils "2015.03.30T23.14.19.79ff61f"]
   [ring.middleware.logger "0.5.0" :exclusions [org.slf4j/slf4j-log4j12]]
   [ring.middleware.conditional "0.1.0"]
   [com.example/the-config "2014.11.07T23.39.35.5844ff4"]
   [com.example.the-request/the-request-core "2015.04.23T22.02.36.e4ca089"]
   [com.example/the-ring-middleware "2014.11.07T23.39.49.0d0d85d"]]

Solution

  • The "magic" is in the defstatus macro:

    (defmacro defstatus [class-name status name description & [options]]
    

    see line 29 and 45:

    (let [...
          fn-name (->kebab-case class-name)
          ...
    
    `(defn ~(symbol (str fn-name "!"))
        ...
    

    So (defstatus Forbidden ...) expands to (defn forbidden! ...)

    You can see the full macro expansion by executing:

    (macroexpand-1 '(defstatus Forbidden 403 "Forbidden" "The request was a legal request but the server is refusing to respond to it."))
    

    In the ring.util.http-response namespace.

    class-name is Forbidden

    (->kebab-case Forbidden) => forbidden

    (str forbidden "!") => "forbidden!"

    (defn ~(symbol "fobidden!") ...) => (defn forbidden! ...)

    Macros expand to code... in this case the code to define a function similarly named to the first argument, but camel-cased and with a trailing bang.