diff --git a/CHANGELOG.md b/CHANGELOG.md index 727abb8..147bca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.4.2 + +- Join path with baseurl using a `/` where necessary +- Allow to specify the Content-Type and Authorization request headers manually + ## 0.4.1 - Include httpkit error in exception when available diff --git a/project.clj b/project.clj index ede7ed1..c24c9a5 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject dev.nubank/clj-github "0.4.1" +(defproject dev.nubank/clj-github "0.4.2" :description "A Clojure library for interacting with the github developer API" :url "https://github.com/nubank/clj-github" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" diff --git a/src/clj_github/httpkit_client.clj b/src/clj_github/httpkit_client.clj index 64de116..10c0e22 100644 --- a/src/clj_github/httpkit_client.clj +++ b/src/clj_github/httpkit_client.clj @@ -11,14 +11,20 @@ (defn get-installation-token [{:keys [token-fn]}] (token-fn)) +(defn- append-url-path [baseurl path] + (str baseurl (when-not (or (.endsWith baseurl "/") + (.startsWith path "/")) + "/") path)) + (defn- prepare - [{:keys [token-fn]} {:keys [path method body] :or {method :get} :as request}] + [{:keys [token-fn]} {:keys [path method body] :or {method :get path ""} :as request}] (-> request (assoc :method method) (assoc-some :body (and body (cheshire/generate-string body))) - (assoc :url (str github-url path)) - (assoc-in [:headers "Content-Type"] "application/json") - (assoc-in [:headers "Authorization"] (str "Bearer " (token-fn))))) + (assoc :url (append-url-path github-url path)) + ;; TODO http headers are case insensitive, also this could be keywords + (update-in [:headers "Content-Type"] #(or % "application/json")) + (update-in [:headers "Authorization"] #(or % (str "Bearer " (token-fn)))))) (defn- parse-body [content-type body] (if (and content-type (re-find #"application/json" content-type)) @@ -30,14 +36,16 @@ (get-in response [:headers "Content-Type"]))) (defn request [client req-map] - (let [response @(httpkit/request (prepare client req-map))] + (let [request (prepare client req-map) + response @(httpkit/request request)] (if (success-codes (:status response)) (update response :body (partial parse-body (content-type response))) (throw (ex-info "Request to GitHub failed" {:response (select-keys response [:status :body])} (:error response)))))) -(defn new-client [{:keys [app-id private-key token org] :as opts}] +(defn new-client [{:keys [app-id private-key token org token-fn] :as opts}] + {:pre [(or token app-id token-fn)]} (cond token {:token-fn (constantly token)} diff --git a/test/clj_github/httpkit_client_test.clj b/test/clj_github/httpkit_client_test.clj index 7733f17..6ccbb1f 100644 --- a/test/clj_github/httpkit_client_test.clj +++ b/test/clj_github/httpkit_client_test.clj @@ -24,6 +24,10 @@ (with-fake-http [{:url "https://api.github.com/path"} {:status 200}] (is (match? {:status 200} (sut/request client {:path "/path"}))))) + (testing "path is appended to url with optional slash" + (with-fake-http [{:url "https://api.github.com/path"} + {:status 200}] + (is (match? {:status 200} (sut/request client {:path "path"}))))) (testing "github token is added to authorization header" (with-fake-http [{:headers {"Authorization" "Bearer token" "Content-Type" "application/json"}} @@ -43,4 +47,28 @@ (with-fake-http [{} {:error cause :status nil}] (let [e (try (sut/request client {}) (catch Exception e e))] (is (re-matches #"(?i)Request to GitHub failed" (.getMessage e))) - (is (= cause (.getCause e))))))))) + (is (= cause (.getCause e))))))) + (testing "request headers" + (testing "Can use custom headers" + (with-fake-http [(fn [req] + (is (= "test" (get-in req [:headers "Test-Header"])))) + {:status 200}] + ;; assertion is in fake http callback + (sut/request client {:headers {"Test-Header" "test"}}))) + (testing "Can override authentication" + ;; clj-github sets authorization header unless specified. Beware that the + ;; implementation here is case sensitive whereas HTTP headers are not. + (with-fake-http [(fn [req] + (is (= "Test SomeValue" (get-in req [:headers "Authorization"])))) + {:status 200}] + ;; assertion is in fake http callback + (sut/request client {:headers {"Authorization" "Test SomeValue"}}))) + (testing "Can override content-type" + ;; clj-github sets the content-type header unless specified. Beware that the + ;; implementation here is case sensitive whereas HTTP headers are not. + (with-fake-http [(fn [req] + (is (= "test/test" (get-in req [:headers "Content-Type"])))) + {:status 200}] + ;; assertion is in fake http callback + (sut/request client {:headers {"Content-Type" "test/test"}})))))) +