Skip to content

Instantly share code, notes, and snippets.

@jmorton
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmorton/4c20ce774af51d5bb098 to your computer and use it in GitHub Desktop.
Save jmorton/4c20ce774af51d5bb098 to your computer and use it in GitHub Desktop.
(ns accept.core)
;; Content Type matters in a REST API.
(defrecord Accept [media-range quality accept-extension])
(defn accept [str]
"Parse a single accept string into a map"
; according to RFC2616, the "q" parameter must precede the accept-extension
(let [pattern #"([^;]+)\s*(?:;q=([0-9+\.]+))?\s*(;.+)*"
matches (re-find pattern str)
[_ media-range qvalue accept-extension] matches
quality (java.lang.Double/parseDouble (or qvalue "1"))]
(Accept. media-range quality accept-extension)))
(defn accept-list [str]
"Build a sorted list of parsed accept values"
(sort-by :quality > (map accept (clojure.string/split str #",\s*"))))
(defrecord Address [street city state])
(def here (Address. "710 S. Euclid" "Sioux Falls" "South Dakota"))
(defrecord Person [name age address])
(def me (Person. "Jon" 35 here))
(def you (Person. "Tom" 37 here))
(def wanting-a (accept-list "text/html"))
(def wanting-b (accept-list "application/json, text/html;q=0.9"))
(def wanting-c (accept-list "text/*, application/xml;q=0.9"))
;; Issues with this approach:
;; 1. No notion of object type...
;; 2. Doesn't set response header, no access to response
;; 3. Explicit wild car mapping.
;; 4. Maybe render takes request, response, object. Now the library is
;; coupled to ring like applications? So, move that render to a
;; ring specific namespace.
(defmulti render
(fn [obj accept] ; accept might be request...
[(:media-range accept) (type obj)]))
(defmethod render ["text/html" Person] [obj _]
(str "Behold, a person, I render " (:name obj) " as HTML"))
(defmethod render ["text/plain" Person] [obj _]
(str "Behold, a person, I render " (:name obj) " as plain text"))
(defmethod render ["application/json" Person] [obj _]
(str "{'name':'" (:name obj) "'}"))
(defmethod render ["application/xml" Person] [obj _]
(str "<person>" (:name obj) "</person>"))
(defmethod render ["text/*" Person] [obj accept]
(render obj { :type "text" :subtype "html" }))
(defmethod render ["*/*" Person] [obj accept]
(render obj { :type "text" :subtype "html" }))
(render me {:media-range "text/foo"})
(render here {:media-range "text/html"})
;(render you {:type "text" :subtype "plain"})
;(render me {:type "*" :subtype "*"})
;(def as [{:type :image :subtype :*} {:type :text :subtype :*}])
;(render me as)
@jmorton
Copy link
Author

jmorton commented Feb 24, 2015

It'd be nice to have a way to get a list of the content types that the Person class... "supports" ...

@jmorton
Copy link
Author

jmorton commented Feb 24, 2015

Reverse defmethod pattern to be obj-type then content-type

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment