Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Fetching contributors…
Cannot retrieve contributors at this time
108 lines (92 sloc) 4.18 KB
(ns tentacles.core
(:require [clj-http.client :as http]
[cheshire.core :as json]
[clojure.string :as str]
[cemerick.url :as url])
(def ^:dynamic url "")
(def ^:dynamic defaults {})
(defn query-map
"Merge defaults, turn keywords into strings, and replace hyphens with underscores."
(into {}
(for [[k v] (concat defaults entries)]
[(.replace (name k) "-" "_") v])))
(defn parse-json
"Same as json/parse-string but handles nil gracefully."
[s] (when s (json/parse-string s true)))
(defn parse-link [link]
(let [[_ url] (re-find #"<(.*)>" link)
[_ rel] (re-find #"rel=\"(.*)\"" link)]
[(keyword rel) url]))
(defn parse-links
"Takes the content of the link header from a github resp, returns a map of links"
(->> (str/split link-body #",")
(map parse-link)
(into {})))
(defn merge-rate-limit [m h]
"Merges RateLimit values from headers into Json response"
(merge m (select-keys h [:X-RateLimit-Limit :X-RateLimit-Remaining])))
(defn safe-parse
"Takes a response and checks for certain status codes. If 204, return nil.
If 400, 401, 204, 422, 403, 404 or 500, return the original response with the body parsed
as json. Otherwise, parse and return the body if json, or return the body if raw."
(if (#{400 401 204 422 403 404 500} (:status resp))
(update-in resp [:body] parse-json)
(let [links (parse-links (get-in resp [:headers "link"] ""))
content-type (get-in resp [:headers "content-type"])]
(if-not (.contains content-type "raw")
(with-meta (merge-rate-limit (parse-json (:body resp)) (:headers resp)) {:links links})
(resp :body)))))
(defn update-req
"Given a clj-http request, and a 'next' url string, merge the next url into the request"
[req url]
(let [url-map (url/url url)]
(assoc-in req [:query-params] (-> url-map :query))))
(defn no-content?
"Takes a response and returns true if it is a 204 response, false otherwise."
[x] (= (:status x) 204))
(defn format-url
"Creates a URL out of end-point and positional. Called URLEncoder/encode on
the elements of positional and then formats them in."
[end-point positional]
(str url (apply format end-point (map #(URLEncoder/encode (str %) "UTF-8") positional))))
(defn make-request [method end-point positional query]
(let [all-pages? (query "all_pages")
req (merge-with merge
{:url (format-url end-point positional)
:basic-auth (query "auth")
:throw-exceptions (or (query "throw_exceptions") false)
:method method}
(when (query "accept")
{:headers {"Accept" (query "accept")}})
(when (query "oauth_token")
{:headers {"Authorization" (str "token " (query "oauth_token"))}}))
proper-query (dissoc query "auth" "oauth_token" "all_pages" "accept")
req (if (#{:post :put :delete} method)
(assoc req :body (json/generate-string (or (proper-query "raw") proper-query)))
(assoc req :query-params proper-query))
exec-request-one (fn exec-request-one [req]
(safe-parse (http/request req)))
exec-request (fn exec-request [req]
(let [resp (exec-request-one req)]
(if (and all-pages? (-> resp meta :links :next))
(let [new-req (update-req req (-> resp meta :links :next))]
(lazy-cat resp (exec-request new-req)))
(exec-request req)))
(defn api-call
([method end-point] (api-call method end-point nil nil))
([method end-point positional] (api-call method end-point positional nil))
([method end-point positional query]
(let [query (query-map query)]
(make-request method end-point positional query))))
(defn rate-limit [] (api-call :get "rate_limit"))
(defmacro with-url [new-url & body]
`(binding [url ~new-url]
(defmacro with-defaults [options & body]
`(binding [defaults ~options]
Jump to Line
Something went wrong with that request. Please try again.