Skip to content
This repository has been archived by the owner on Aug 24, 2022. It is now read-only.

Commit

Permalink
finished a la carte formats
Browse files Browse the repository at this point in the history
  • Loading branch information
ngrunwald committed Feb 20, 2013
1 parent ffe8cd2 commit 18cd265
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 47 deletions.
4 changes: 3 additions & 1 deletion src/ring/middleware/format.clj
Expand Up @@ -5,6 +5,8 @@

(defn wrap-restful-format
[handler & {:keys [formats]
:or {formats [:json :edn :yaml :yaml-in-html]}
:as options}]
(-> handler
))
(par/wrap-restful-params :formats formats)
(res/wrap-restful-response :formats formats)))
48 changes: 22 additions & 26 deletions src/ring/middleware/format_params.clj
Expand Up @@ -77,24 +77,22 @@
the Exception"
[handler & {:keys [predicate decoder charset handle-error]}]
(fn [{:keys [#^InputStream body] :as req}]
(let [mod-req
(try
(if-let [byts (slurp-to-bytes body)]
(if (predicate req)
(let [body (:body req)
#^String char-enc (if (string? charset) charset (charset (assoc req :body byts)))
bstr (String. byts char-enc)
fmt-params (decoder bstr)
req* (assoc req
:body-params fmt-params
:params (merge (:params req)
(when (map? fmt-params) fmt-params)))]
req*)
(assoc req :body (ByteArrayInputStream. byts)))
req)
(catch Exception e
(handle-error e handler req)))]
(handler mod-req))))
(try
(if-let [byts (slurp-to-bytes body)]
(if (predicate req)
(let [body (:body req)
#^String char-enc (if (string? charset) charset (charset (assoc req :body byts)))
bstr (String. byts char-enc)
fmt-params (decoder bstr)
req* (assoc req
:body-params fmt-params
:params (merge (:params req)
(when (map? fmt-params) fmt-params)))]
(handler req*))
(handler (assoc req :body (ByteArrayInputStream. byts))))
(handler req))
(catch Exception e
(handle-error e handler req)))))

(def json-request?
(make-type-request-pred #"^application/(vnd.+)?json"))
Expand Down Expand Up @@ -141,7 +139,7 @@
(safe-read-string s)))

(def clojure-request?
(make-type-request-pred #"^application/(vnd.+)?(x-)?clojure"))
(make-type-request-pred #"^application/(vnd.+)?(x-)?(clojure|edn)"))

(defn wrap-clojure-params
"Handles body params in Clojure format. See wrap-format-params for details."
Expand All @@ -159,10 +157,7 @@
(def format-wrappers
{:json wrap-json-params
:edn wrap-clojure-params
:clj wrap-clojure-params
:clojure wrap-clojure-params
:yaml wrap-yaml-params
:yml wrap-yaml-params})
:yaml wrap-yaml-params})

(defn wrap-restful-params
"Wrapper that tries to do the right thing with the request :body and provide
Expand All @@ -172,8 +167,9 @@
:or {handle-error default-handle-error
formats [:json :edn :yaml]}}]
(reduce (fn [h format]
(if-let [wrapper (format-wrappers (keyword format))]
(if-let [wrapper (if
(fn? format) format
(format-wrappers (keyword format)))]
(wrapper h :handle-error handle-error)
(throw (java.util.UnknownFormatFlagsException.
(format "wrap-restful-params does not recognize format %s" (keyword format))))))
h))
handler formats))
54 changes: 37 additions & 17 deletions src/ring/middleware/format_response.clj
Expand Up @@ -20,13 +20,11 @@
"Check whether encoder can encode to accepted-type.
Accepted-type should have keys :type and :sub-type with appropriate
values."
[encoder accepted-type]
(let [type (accepted-type :type)
sub-type (accepted-type :sub-type)]
(or (= "*" type)
(and (= (get-in encoder [:enc-type :type]) type)
(or (= "*" sub-type)
(= (get-in encoder [:enc-type :sub-type]) sub-type))))))
[{:keys [enc-type] :as encoder} {:keys [type sub-type] :as accepted-type}]
(or (= "*" type)
(and (= (:type enc-type) type)
(or (= "*" sub-type)
(= (enc-type :sub-type) sub-type)))))

(defn sort-by-check
[by check headers]
Expand Down Expand Up @@ -66,7 +64,7 @@
(sort-by-check :sub-type "*")
(sort-by :q >)))

(def parse-accept-header (memo-lu parse-accept-header* 100))
(def parse-accept-header (memo-lu parse-accept-header* 500))

(defn preferred-encoder
"Return the encoder that encodes to the most preferred type.
Expand Down Expand Up @@ -218,6 +216,7 @@
(def format-encoders
{:json (make-encoder json/generate-string "application/json")
:edn (make-encoder generate-native-clojure "application/edn")
:clojure (make-encoder generate-native-clojure "application/clojure")
:yaml (make-encoder yaml/generate-string "application/x-yaml")
:yaml-in-html (make-encoder wrap-yaml-in-html "text/html")})

Expand All @@ -229,17 +228,38 @@
[handler & {:keys [handle-error formats charset]
:or {handle-error default-handle-error
charset "utf-8"
formats [:json :yaml :edn :yaml-in-html]}}]
(let [encoders (into []
(map (fn [format]
(if-let [encoder (get format-encoders (keyword format))]
encoder
(throw
(java.util.UnknownFormatFlagsException.
(format "wrap-restful-response does not recognize format %s" (keyword format))))))
formats))]
formats [:json :yaml :edn :clojure :yaml-in-html]}}]
(let [encoders (for [format formats
:when format
:let [encoder (if (map? format)
format
(get format-encoders (keyword format)))]
:when encoder]
encoder)]
(wrap-format-response handler
:predicate serializable?
:encoders encoders
:charset charset
:handle-error handle-error)))

(defn wrap-restful-response
"Wrapper that tries to do the right thing with the response :body
and provide a solid basis for a RESTful API. It will serialize to
JSON, YAML, Clojure or HTML-wrapped YAML depending on Accept header.
See wrap-format-response for more details."
[handler & {:keys [handle-error formats charset]
:or {handle-error default-handle-error
charset "utf-8"
formats [:json :yaml :edn :clojure :yaml-in-html]}}]
(let [encoders (for [format formats
:when format
:let [encoder (if (map? format)
format
(get format-encoders (keyword format)))]
:when encoder]
encoder)]
(wrap-format-response handler
:predicate serializable?
:encoders encoders
:charset charset
:handle-error handle-error)))
41 changes: 41 additions & 0 deletions test/ring/middleware/test/format.clj
@@ -0,0 +1,41 @@
(ns ring.middleware.test.format
(:use [clojure.test]
[ring.middleware.format])
(:require [cheshire.core :as json]
[clj-yaml.core :as yaml])
(:import [java.io ByteArrayInputStream]))

(defn stream [s]
(ByteArrayInputStream. (.getBytes s "UTF-8")))

(def restful-echo
(wrap-restful-format (fn [req] (assoc req :body (vals (:body-params req))))))

(def restful-echo-json
(wrap-restful-format (fn [req] (assoc req :body (vals (:body-params req))))
:format [:json]))

(deftest test-restful-round-trip
(let [ok-accept "application/edn"
msg {:test :ok}
ok-req {:headers {"accept" ok-accept}
:content-type ok-accept
:body (stream (pr-str msg))}
r-trip (restful-echo ok-req)]
(is (= (get-in r-trip [:headers "Content-Type"])
"application/edn; charset=utf-8"))
(is (= (read-string (slurp (:body r-trip))) (vals msg)))
(is (= (:params r-trip) msg))
(is (thrown? RuntimeException (restful-echo {:headers {"accept" "foo/bar"}}))))
(let [ok-accept "application/json"
msg {"test" "ok"}
ok-req {:headers {"accept" ok-accept}
:content-type ok-accept
:body (stream (json/encode msg))}
r-trip (restful-echo-json ok-req)]
(is (= (get-in r-trip [:headers "Content-Type"])
"application/json; charset=utf-8"))
(is (= (json/decode (slurp (:body r-trip))) (vals msg)))
(is (= (:params r-trip) msg))
(is (nil? (:body-params (restful-echo-json {:headers {"accept" "application/edn"}
:content-type "application/edn"}))))))
6 changes: 3 additions & 3 deletions test/ring/middleware/test/format_response.clj
Expand Up @@ -140,9 +140,9 @@

(def custom-restful-echo
(wrap-restful-response identity
:default {:encoder (constantly "foobar")
:enc-type {:type "text"
:sub-type "foo"}}))
:formats [{:encoder (constantly "foobar")
:enc-type {:type "text"
:sub-type "foo"}}]))

(deftest format-custom-restful-hashmap
(let [req {:body {:foo "bar"} :headers {"accept" "text/foo"}}
Expand Down

0 comments on commit 18cd265

Please sign in to comment.