From afb7e31e217c276c08c145c266dd4393460c2a90 Mon Sep 17 00:00:00 2001 From: xhh Date: Thu, 3 Dec 2015 13:25:59 +0800 Subject: [PATCH 1/2] Add authentications support to Clojure client Closes #1654 --- .../src/main/resources/clojure/api.mustache | 3 +- .../src/main/resources/clojure/core.mustache | 101 ++++++++++++------ .../clojure/src/swagger_petstore/api/pet.clj | 24 +++-- .../src/swagger_petstore/api/store.clj | 12 ++- .../clojure/src/swagger_petstore/api/user.clj | 24 +++-- .../clojure/src/swagger_petstore/core.clj | 97 ++++++++++++----- .../test/swagger_petstore/api/pet_test.clj | 7 ++ .../test/swagger_petstore/api/store_test.clj | 7 ++ .../test/swagger_petstore/api/user_test.clj | 7 ++ .../test/swagger_petstore/core_test.clj | 48 +++++++-- 10 files changed, 241 insertions(+), 89 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/clojure/api.mustache b/modules/swagger-codegen/src/main/resources/clojure/api.mustache index 94e95dffaf7..2993f118b57 100644 --- a/modules/swagger-codegen/src/main/resources/clojure/api.mustache +++ b/modules/swagger-codegen/src/main/resources/clojure/api.mustache @@ -15,5 +15,6 @@ <#hasOptionalParams> :form-params {<#formParams>"" }<#bodyParam> <#hasOptionalParams> :body-param <#hasOptionalParams> :content-types [<#consumes>""<#hasMore> ] - <#hasOptionalParams> :accepts [<#produces>""<#hasMore> ]}))<#hasOptionalParams>) + <#hasOptionalParams> :accepts [<#produces>""<#hasMore> ] + <#hasOptionalParams> :auth-names [<#authMethods>"<&name>"<#hasMore> ]})<#hasOptionalParams>)) \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/clojure/core.mustache b/modules/swagger-codegen/src/main/resources/clojure/core.mustache index 32aa8a6bc0d..7ed6195a671 100644 --- a/modules/swagger-codegen/src/main/resources/clojure/core.mustache +++ b/modules/swagger-codegen/src/main/resources/clojure/core.mustache @@ -1,4 +1,4 @@ -(ns {{{baseNamespace}}}.core +{{=< >=}}(ns <&baseNamespace>.core (:require [cheshire.core :refer [generate-string parse-string]] [clojure.string :as str] [clj-http.client :as client]) @@ -7,12 +7,18 @@ (java.util Date TimeZone) (java.text SimpleDateFormat))) +(def auth-definitions + {<#authMethods>"<&name>" <#isBasic>{:type :basic}<#isApiKey>{:type :api-key<#isKeyInHeader> :in :header<#isKeyInQuery> :in :query :param-name "<&keyParamName>"}<#isOAuth>{:type :oauth2}<#hasMore> + }) + (def default-api-context "Default API context." - {:base-url "http://petstore.swagger.io/v2" + {:base-url "<&basePath>" :date-format "yyyy-MM-dd" :datetime-format "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - :debug false}) + :debug false + :auths {<#authMethods>"<&name>" nil<#hasMore> + }}) (def ^:dynamic *api-context* "Dynamic API context to be applied in API calls." @@ -20,12 +26,16 @@ (defmacro with-api-context "A helper macro to wrap *api-context* with default values." - [context & body] - `(binding [*api-context* (merge *api-context* ~context)] - ~@body)) + [api-context & body] + `(let [api-context# ~api-context + api-context# (-> *api-context* + (merge api-context#) + (assoc :auths (merge (:auths *api-context*) (:auths api-context#))))] + (binding [*api-context* api-context#] + ~@body))) (defmacro check-required-params - "Throw exception if the given parameter value is nil." + "Throw exception if any of the given parameters is nil." [& params] (->> params (map (fn [p] @@ -33,9 +43,9 @@ (throw (IllegalArgumentException. ~(str "The parameter \"" p "\" is required")))))) (list* 'do))) -(defn- make-date-format - ([format-str] (make-date-format format-str nil)) - ([format-str time-zone] +(defn- ^SimpleDateFormat make-date-format + ([^String format-str] (make-date-format format-str nil)) + ([^String format-str ^String time-zone] (let [date-format (SimpleDateFormat. format-str)] (when time-zone (.setTimeZone date-format (TimeZone/getTimeZone time-zone))) @@ -75,18 +85,45 @@ (-> (make-date-format datetime-format time-zone) (.parse s))))) -(defn param-to-str [param] +(defn param->str "Format the given parameter value to string." + [param] (cond (instance? Date param) (format-datetime param) (sequential? param) (str/join "," param) :else (str param))) +(defn auth->opts + "Process the given auth to an option map that might conatin request options and parameters." + [{:keys [type in param-name]} value] + (case type + :basic {:req-opts {:basic-auth value}} + :oauth2 {:req-opts {:oauth-token value}} + :api-key (case in + :header {:header-params {param-name value}} + :query {:query-params {param-name value}} + (throw (IllegalArgumentException. (str "Invalid `in` for api-key auth: " in)))) + (throw (IllegalArgumentException. (str "Invalid auth `type`: " type))))) + +(defn process-auth + "Process the given auth name into options, which is merged into the given opts." + [opts auth-name] + (if-let [value (get-in *api-context* [:auths auth-name])] + (merge-with merge + opts + (auth->opts (get auth-definitions auth-name) value)) + opts)) + +(defn auths->opts + "Process the given auth names to an option map that might conatin request options and parameters." + [auth-names] + (reduce process-auth {} auth-names)) + (defn make-url "Make full URL by adding base URL and filling path parameters." [path path-params] (let [path (reduce (fn [p [k v]] - (str/replace p (re-pattern (str "\\{" k "\\}")) (param-to-str v))) + (str/replace p (re-pattern (str "\\{" k "\\}")) (param->str v))) path path-params)] (str (:base-url *api-context*) path))) @@ -95,15 +132,15 @@ "Normalize parameter value, handling three cases: for sequential value, normalize each elements of it; for File value, do nothing with it; - otherwise, call `param-to-string`." + otherwise, call `param->str`." [param] (cond (sequential? param) (map normalize-param param) (instance? File param) param - :else (param-to-str param))) + :else (param->str param))) (defn normalize-params - "Normalize parameters values: remove nils, format to string with `param-to-str`." + "Normalize parameters values: remove nils, format to string with `param->str`." [params] (->> params (remove (comp nil? second)) @@ -144,7 +181,7 @@ ;; for non-JSON response, return the body string directly :else body)) -(defn form-params-to-multipart +(defn form-params->multipart "Convert the given form parameters map into a vector as clj-http's :multipart option." [form-params] (->> form-params @@ -153,25 +190,27 @@ (defn call-api "Call an API by making HTTP request and return its response." - [path method {:keys [path-params query-params header-params form-params body-param content-types accepts]}] + [path method {:keys [path-params body-param content-types accepts auth-names] :as opts}] (let [{:keys [debug]} *api-context* + {:keys [req-opts query-params header-params form-params]} (auths->opts auth-names) + query-params (merge query-params (:query-params opts)) + header-params (merge header-params (:header-params opts)) + form-params (merge form-params (:form-params opts)) url (make-url path path-params) - content-type (or (json-preferred-mime content-types) - (and body-param :json)) + content-type (or (json-preferred-mime content-types) (and body-param :json)) accept (or (json-preferred-mime accepts) :json) multipart? (= "multipart/form-data" content-type) - opts (cond-> {:url url :method method} - accept (assoc :accept accept) - (seq query-params) (assoc :query-params (normalize-params query-params)) - (seq header-params) (assoc :header-params (normalize-params header-params)) - (and content-type (not multipart?)) (assoc :content-type content-type) - multipart? (assoc :multipart (-> form-params - normalize-params - form-params-to-multipart)) - (and (not multipart?) (seq form-params)) (assoc :form-params (normalize-params form-params)) - body-param (assoc :body (serialize body-param content-type)) - debug (assoc :debug true :debug-body true)) - resp (client/request opts)] + req-opts (cond-> req-opts + true (assoc :url url :method method) + accept (assoc :accept accept) + (seq query-params) (assoc :query-params (normalize-params query-params)) + (seq header-params) (assoc :headers (normalize-params header-params)) + (and content-type (not multipart?)) (assoc :content-type content-type) + multipart? (assoc :multipart (-> form-params normalize-params form-params->multipart)) + (and (not multipart?) (seq form-params)) (assoc :form-params (normalize-params form-params)) + body-param (assoc :body (serialize body-param content-type)) + debug (assoc :debug true :debug-body true)) + resp (client/request req-opts)] (when debug (println "Response:") (println resp)) diff --git a/samples/client/petstore/clojure/src/swagger_petstore/api/pet.clj b/samples/client/petstore/clojure/src/swagger_petstore/api/pet.clj index c331444c886..ac4b921ba1e 100644 --- a/samples/client/petstore/clojure/src/swagger_petstore/api/pet.clj +++ b/samples/client/petstore/clojure/src/swagger_petstore/api/pet.clj @@ -14,7 +14,8 @@ :form-params {} :body-param body :content-types ["application/json" "application/xml"] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names ["petstore_auth"]}))) (defn add-pet "Add a new pet to the store @@ -28,7 +29,8 @@ :form-params {} :body-param body :content-types ["application/json" "application/xml"] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names ["petstore_auth"]}))) (defn find-pets-by-status "Finds Pets by status @@ -41,7 +43,8 @@ :query-params {"status" status } :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names ["petstore_auth"]}))) (defn find-pets-by-tags "Finds Pets by tags @@ -54,7 +57,8 @@ :query-params {"tags" tags } :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names ["petstore_auth"]}))) (defn get-pet-by-id "Find pet by ID @@ -66,7 +70,8 @@ :query-params {} :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]})) + :accepts ["application/json" "application/xml"] + :auth-names ["api_key"]})) (defn update-pet-with-form "Updates a pet in the store with form data @@ -79,7 +84,8 @@ :query-params {} :form-params {"name" name "status" status } :content-types ["application/x-www-form-urlencoded"] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names ["petstore_auth"]}))) (defn delete-pet "Deletes a pet @@ -92,7 +98,8 @@ :query-params {} :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names ["petstore_auth"]}))) (defn upload-file "uploads an image @@ -105,4 +112,5 @@ :query-params {} :form-params {"additionalMetadata" additional-metadata "file" file } :content-types ["multipart/form-data"] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names ["petstore_auth"]}))) diff --git a/samples/client/petstore/clojure/src/swagger_petstore/api/store.clj b/samples/client/petstore/clojure/src/swagger_petstore/api/store.clj index f2a18cea2e8..054b3671fa6 100644 --- a/samples/client/petstore/clojure/src/swagger_petstore/api/store.clj +++ b/samples/client/petstore/clojure/src/swagger_petstore/api/store.clj @@ -12,7 +12,8 @@ :query-params {} :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]})) + :accepts ["application/json" "application/xml"] + :auth-names ["api_key"]})) (defn place-order "Place an order for a pet @@ -26,7 +27,8 @@ :form-params {} :body-param body :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names []}))) (defn get-order-by-id "Find purchase order by ID @@ -38,7 +40,8 @@ :query-params {} :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]})) + :accepts ["application/json" "application/xml"] + :auth-names []})) (defn delete-order "Delete purchase order by ID @@ -50,4 +53,5 @@ :query-params {} :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]})) + :accepts ["application/json" "application/xml"] + :auth-names []})) diff --git a/samples/client/petstore/clojure/src/swagger_petstore/api/user.clj b/samples/client/petstore/clojure/src/swagger_petstore/api/user.clj index 15d41515ebb..2e5c5782e04 100644 --- a/samples/client/petstore/clojure/src/swagger_petstore/api/user.clj +++ b/samples/client/petstore/clojure/src/swagger_petstore/api/user.clj @@ -14,7 +14,8 @@ :form-params {} :body-param body :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names []}))) (defn create-users-with-array-input "Creates list of users with given input array @@ -28,7 +29,8 @@ :form-params {} :body-param body :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names []}))) (defn create-users-with-list-input "Creates list of users with given input array @@ -42,7 +44,8 @@ :form-params {} :body-param body :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names []}))) (defn login-user "Logs user into the system @@ -55,7 +58,8 @@ :query-params {"username" username "password" password } :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names []}))) (defn logout-user "Logs out current logged in user session @@ -67,7 +71,8 @@ :query-params {} :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]})) + :accepts ["application/json" "application/xml"] + :auth-names []})) (defn get-user-by-name "Get user by user name @@ -79,7 +84,8 @@ :query-params {} :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]})) + :accepts ["application/json" "application/xml"] + :auth-names []})) (defn update-user "Updated user @@ -93,7 +99,8 @@ :form-params {} :body-param body :content-types [] - :accepts ["application/json" "application/xml"]}))) + :accepts ["application/json" "application/xml"] + :auth-names []}))) (defn delete-user "Delete user @@ -105,4 +112,5 @@ :query-params {} :form-params {} :content-types [] - :accepts ["application/json" "application/xml"]})) + :accepts ["application/json" "application/xml"] + :auth-names []})) diff --git a/samples/client/petstore/clojure/src/swagger_petstore/core.clj b/samples/client/petstore/clojure/src/swagger_petstore/core.clj index e7c55258e79..50312c3d150 100644 --- a/samples/client/petstore/clojure/src/swagger_petstore/core.clj +++ b/samples/client/petstore/clojure/src/swagger_petstore/core.clj @@ -7,12 +7,18 @@ (java.util Date TimeZone) (java.text SimpleDateFormat))) +(def auth-definitions + {"petstore_auth" {:type :oauth2} + "api_key" {:type :api-key :in :header :param-name "api_key"}}) + (def default-api-context "Default API context." {:base-url "http://petstore.swagger.io/v2" :date-format "yyyy-MM-dd" :datetime-format "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - :debug false}) + :debug false + :auths {"petstore_auth" nil + "api_key" nil}}) (def ^:dynamic *api-context* "Dynamic API context to be applied in API calls." @@ -20,12 +26,16 @@ (defmacro with-api-context "A helper macro to wrap *api-context* with default values." - [context & body] - `(binding [*api-context* (merge *api-context* ~context)] - ~@body)) + [api-context & body] + `(let [api-context# ~api-context + api-context# (-> *api-context* + (merge api-context#) + (assoc :auths (merge (:auths *api-context*) (:auths api-context#))))] + (binding [*api-context* api-context#] + ~@body))) (defmacro check-required-params - "Throw exception if the given parameter value is nil." + "Throw exception if any of the given parameters is nil." [& params] (->> params (map (fn [p] @@ -33,9 +43,9 @@ (throw (IllegalArgumentException. ~(str "The parameter \"" p "\" is required")))))) (list* 'do))) -(defn- make-date-format - ([format-str] (make-date-format format-str nil)) - ([format-str time-zone] +(defn- ^SimpleDateFormat make-date-format + ([^String format-str] (make-date-format format-str nil)) + ([^String format-str ^String time-zone] (let [date-format (SimpleDateFormat. format-str)] (when time-zone (.setTimeZone date-format (TimeZone/getTimeZone time-zone))) @@ -75,18 +85,45 @@ (-> (make-date-format datetime-format time-zone) (.parse s))))) -(defn param-to-str [param] +(defn param->str "Format the given parameter value to string." + [param] (cond (instance? Date param) (format-datetime param) (sequential? param) (str/join "," param) :else (str param))) +(defn auth->opts + "Process the given auth to an option map that might conatin request options and parameters." + [{:keys [type in param-name]} value] + (case type + :basic {:req-opts {:basic-auth value}} + :oauth2 {:req-opts {:oauth-token value}} + :api-key (case in + :header {:header-params {param-name value}} + :query {:query-params {param-name value}} + (throw (IllegalArgumentException. (str "Invalid `in` for api-key auth: " in)))) + (throw (IllegalArgumentException. (str "Invalid auth `type`: " type))))) + +(defn process-auth + "Process the given auth name into options, which is merged into the given opts." + [opts auth-name] + (if-let [value (get-in *api-context* [:auths auth-name])] + (merge-with merge + opts + (auth->opts (get auth-definitions auth-name) value)) + opts)) + +(defn auths->opts + "Process the given auth names to an option map that might conatin request options and parameters." + [auth-names] + (reduce process-auth {} auth-names)) + (defn make-url "Make full URL by adding base URL and filling path parameters." [path path-params] (let [path (reduce (fn [p [k v]] - (str/replace p (re-pattern (str "\\{" k "\\}")) (param-to-str v))) + (str/replace p (re-pattern (str "\\{" k "\\}")) (param->str v))) path path-params)] (str (:base-url *api-context*) path))) @@ -95,15 +132,15 @@ "Normalize parameter value, handling three cases: for sequential value, normalize each elements of it; for File value, do nothing with it; - otherwise, call `param-to-string`." + otherwise, call `param->str`." [param] (cond (sequential? param) (map normalize-param param) (instance? File param) param - :else (param-to-str param))) + :else (param->str param))) (defn normalize-params - "Normalize parameters values: remove nils, format to string with `param-to-str`." + "Normalize parameters values: remove nils, format to string with `param->str`." [params] (->> params (remove (comp nil? second)) @@ -144,7 +181,7 @@ ;; for non-JSON response, return the body string directly :else body)) -(defn form-params-to-multipart +(defn form-params->multipart "Convert the given form parameters map into a vector as clj-http's :multipart option." [form-params] (->> form-params @@ -153,25 +190,27 @@ (defn call-api "Call an API by making HTTP request and return its response." - [path method {:keys [path-params query-params header-params form-params body-param content-types accepts]}] + [path method {:keys [path-params body-param content-types accepts auth-names] :as opts}] (let [{:keys [debug]} *api-context* + {:keys [req-opts query-params header-params form-params]} (auths->opts auth-names) + query-params (merge query-params (:query-params opts)) + header-params (merge header-params (:header-params opts)) + form-params (merge form-params (:form-params opts)) url (make-url path path-params) - content-type (or (json-preferred-mime content-types) - (and body-param :json)) + content-type (or (json-preferred-mime content-types) (and body-param :json)) accept (or (json-preferred-mime accepts) :json) multipart? (= "multipart/form-data" content-type) - opts (cond-> {:url url :method method} - accept (assoc :accept accept) - (seq query-params) (assoc :query-params (normalize-params query-params)) - (seq header-params) (assoc :header-params (normalize-params header-params)) - (and content-type (not multipart?)) (assoc :content-type content-type) - multipart? (assoc :multipart (-> form-params - normalize-params - form-params-to-multipart)) - (and (not multipart?) (seq form-params)) (assoc :form-params (normalize-params form-params)) - body-param (assoc :body (serialize body-param content-type)) - debug (assoc :debug true :debug-body true)) - resp (client/request opts)] + req-opts (cond-> req-opts + true (assoc :url url :method method) + accept (assoc :accept accept) + (seq query-params) (assoc :query-params (normalize-params query-params)) + (seq header-params) (assoc :headers (normalize-params header-params)) + (and content-type (not multipart?)) (assoc :content-type content-type) + multipart? (assoc :multipart (-> form-params normalize-params form-params->multipart)) + (and (not multipart?) (seq form-params)) (assoc :form-params (normalize-params form-params)) + body-param (assoc :body (serialize body-param content-type)) + debug (assoc :debug true :debug-body true)) + resp (client/request req-opts)] (when debug (println "Response:") (println resp)) diff --git a/samples/client/petstore/clojure/test/swagger_petstore/api/pet_test.clj b/samples/client/petstore/clojure/test/swagger_petstore/api/pet_test.clj index 7327b44c45d..281179150a7 100644 --- a/samples/client/petstore/clojure/test/swagger_petstore/api/pet_test.clj +++ b/samples/client/petstore/clojure/test/swagger_petstore/api/pet_test.clj @@ -1,8 +1,15 @@ (ns swagger-petstore.api.pet-test (:require [clojure.test :refer :all] [clojure.java.io :as io] + [swagger-petstore.core :refer [with-api-context]] [swagger-petstore.api.pet :refer :all])) +(defn credentials-fixture [f] + (with-api-context {:auths {"api_key" "special-key"}} + (f))) + +(use-fixtures :once credentials-fixture) + (defn- make-random-pet ([] (make-random-pet nil)) ([{:keys [id] :as attrs :or {id (System/currentTimeMillis)}}] diff --git a/samples/client/petstore/clojure/test/swagger_petstore/api/store_test.clj b/samples/client/petstore/clojure/test/swagger_petstore/api/store_test.clj index 5a453e3693e..cf58b488ed4 100644 --- a/samples/client/petstore/clojure/test/swagger_petstore/api/store_test.clj +++ b/samples/client/petstore/clojure/test/swagger_petstore/api/store_test.clj @@ -1,8 +1,15 @@ (ns swagger-petstore.api.store-test (:require [clojure.test :refer :all] + [swagger-petstore.core :refer [with-api-context]] [swagger-petstore.api.store :refer :all]) (:import (java.util Date))) +(defn credentials-fixture [f] + (with-api-context {:auths {"api_key" "special-key"}} + (f))) + +(use-fixtures :once credentials-fixture) + (defn- make-random-order [] {:id (+ 90000 (rand-int 10000)) :petId 200 diff --git a/samples/client/petstore/clojure/test/swagger_petstore/api/user_test.clj b/samples/client/petstore/clojure/test/swagger_petstore/api/user_test.clj index aa05b23f19a..c24be99c284 100644 --- a/samples/client/petstore/clojure/test/swagger_petstore/api/user_test.clj +++ b/samples/client/petstore/clojure/test/swagger_petstore/api/user_test.clj @@ -1,7 +1,14 @@ (ns swagger-petstore.api.user-test (:require [clojure.test :refer :all] + [swagger-petstore.core :refer [with-api-context]] [swagger-petstore.api.user :refer :all])) +(defn credentials-fixture [f] + (with-api-context {:auths {"api_key" "special-key"}} + (f))) + +(use-fixtures :once credentials-fixture) + (defn- make-random-user ([] (make-random-user nil)) ([{:keys [id] :as attrs :or {id (System/currentTimeMillis)}}] diff --git a/samples/client/petstore/clojure/test/swagger_petstore/core_test.clj b/samples/client/petstore/clojure/test/swagger_petstore/core_test.clj index 394824aa4a9..b68358a860e 100644 --- a/samples/client/petstore/clojure/test/swagger_petstore/core_test.clj +++ b/samples/client/petstore/clojure/test/swagger_petstore/core_test.clj @@ -9,30 +9,43 @@ (is (= {:base-url "http://petstore.swagger.io/v2" :date-format "yyyy-MM-dd" :datetime-format "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - :debug false} + :debug false + :auths {"api_key" nil + "petstore_auth" nil}} default-api-context *api-context* (with-api-context {} *api-context*)))) (testing "customize via with-api-context" - (with-api-context {:base-url "http://localhost" :debug true} + (with-api-context {:base-url "http://localhost" + :debug true + :auths {"api_key" "key1" + "petstore_auth" "token1"}} (is (= {:base-url "http://localhost" :date-format "yyyy-MM-dd" :datetime-format "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - :debug true} + :debug true + :auths {"api_key" "key1" + "petstore_auth" "token1"}} *api-context*)) ;; nested with-api-context inherits values from the outer api context - (with-api-context {:datetime-format "yyyy-MM-dd HH:mm:ss"} + (with-api-context {:datetime-format "yyyy-MM-dd HH:mm:ss" + :auths {"api_key" "key2"}} (is (= {:base-url "http://localhost" :date-format "yyyy-MM-dd" :datetime-format "yyyy-MM-dd HH:mm:ss" - :debug true} + :debug true + :auths {"api_key" "key2" + "petstore_auth" "token1"}} *api-context*)))) ;; back to default api context (is (= {:base-url "http://petstore.swagger.io/v2" :date-format "yyyy-MM-dd" :datetime-format "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - :debug false} + :debug false + :auths {"api_key" nil + "petstore_auth" nil}} + default-api-context *api-context*)))) (deftest test-check-required-params @@ -69,10 +82,10 @@ "2015-11-07T00:49:09-03:00") (is (thrown? ParseException (parse-datetime "2015-11-07T03:49:09.123Z")))))) -(deftest test-param-to-str +(deftest test-param->str (let [date (parse-datetime "2015-11-07T03:49:09.123Z")] (are [param expected] - (is (= expected (param-to-str param))) + (is (= expected (param->str param))) nil "" "abc" "abc" 123 "123" @@ -80,6 +93,25 @@ [12 "34"] "12,34" date (format-datetime date)))) +(deftest test-auths->opts + (testing "auth values not set by default" + (is (= {} (auths->opts ["api_key" "petstore_auth"]))) + (is (= {} (auths->opts [])))) + (testing "set api_key" + (with-api-context {:auths {"api_key" "my key"}} + (is (= {:header-params {"api_key" "my key"}} (auths->opts ["api_key" "petstore_auth"]))) + (is (= {:header-params {"api_key" "my key"}} (auths->opts ["api_key"]))) + (is (= {} (auths->opts ["petstore_auth"]))) + (is (= {} (auths->opts []))))) + (testing "set both api_key and petstore_auth" + (with-api-context {:auths {"api_key" "my key" "petstore_auth" "my token"}} + (is (= {:req-opts {:oauth-token "my token"} + :header-params {"api_key" "my key"}} + (auths->opts ["api_key" "petstore_auth"]))) + (is (= {:req-opts {:oauth-token "my token"}} (auths->opts ["petstore_auth"]))) + (is (= {:header-params {"api_key" "my key"}} (auths->opts ["api_key"]))) + (is (= {} (auths->opts [])))))) + (deftest test-make-url (are [path path-params url] (is (= url (make-url path path-params))) From 9d6b1bb2245fe5b33a72eb2254561f273bcf9d1c Mon Sep 17 00:00:00 2001 From: xhh Date: Thu, 3 Dec 2015 17:25:27 +0800 Subject: [PATCH 2/2] Support collectionFormat for array parameters in Clojure client Closes #1655 --- .../src/main/resources/clojure/api.mustache | 10 +++--- .../src/main/resources/clojure/core.mustache | 36 +++++++++++++++---- .../clojure/src/swagger_petstore/api/pet.clj | 6 ++-- .../src/swagger_petstore/api/store.clj | 2 +- .../clojure/src/swagger_petstore/api/user.clj | 2 +- .../clojure/src/swagger_petstore/core.clj | 36 +++++++++++++++---- .../test/swagger_petstore/core_test.clj | 21 ++++++++--- 7 files changed, 85 insertions(+), 28 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/clojure/api.mustache b/modules/swagger-codegen/src/main/resources/clojure/api.mustache index 2993f118b57..740027e224b 100644 --- a/modules/swagger-codegen/src/main/resources/clojure/api.mustache +++ b/modules/swagger-codegen/src/main/resources/clojure/api.mustache @@ -1,5 +1,5 @@ {{=< >=}}(ns . - (:require [.core :refer [call-api check-required-params]]) + (:require [.core :refer [call-api check-required-params with-collection-format]]) (:import (java.io File))) <#operations><#operation> (defn @@ -9,10 +9,10 @@ <#hasOptionalParams>([<#allParams><#required><#isFile>^File <#hasOptionalParams>{:keys [<#allParams><^required><#isFile>^File ]}]<#hasRequiredParams> <#hasOptionalParams> (check-required-params<#allParams><#required> ) <#hasOptionalParams> (call-api "" : - <#hasOptionalParams> {:path-params {<#pathParams>"" } - <#hasOptionalParams> :header-params {<#headerParams>"" } - <#hasOptionalParams> :query-params {<#queryParams>"" } - <#hasOptionalParams> :form-params {<#formParams>"" }<#bodyParam> + <#hasOptionalParams> {:path-params {<#pathParams>"" <#collectionFormat>(with-collection-format :)<^collectionFormat> } + <#hasOptionalParams> :header-params {<#headerParams>"" <#collectionFormat>(with-collection-format :)<^collectionFormat> } + <#hasOptionalParams> :query-params {<#queryParams>"" <#collectionFormat>(with-collection-format :)<^collectionFormat> } + <#hasOptionalParams> :form-params {<#formParams>"" <#collectionFormat>(with-collection-format :)<^collectionFormat> }<#bodyParam> <#hasOptionalParams> :body-param <#hasOptionalParams> :content-types [<#consumes>""<#hasMore> ] <#hasOptionalParams> :accepts [<#produces>""<#hasMore> ] diff --git a/modules/swagger-codegen/src/main/resources/clojure/core.mustache b/modules/swagger-codegen/src/main/resources/clojure/core.mustache index 7ed6195a671..b05df804a7f 100644 --- a/modules/swagger-codegen/src/main/resources/clojure/core.mustache +++ b/modules/swagger-codegen/src/main/resources/clojure/core.mustache @@ -29,8 +29,8 @@ [api-context & body] `(let [api-context# ~api-context api-context# (-> *api-context* - (merge api-context#) - (assoc :auths (merge (:auths *api-context*) (:auths api-context#))))] + (merge api-context#) + (assoc :auths (merge (:auths *api-context*) (:auths api-context#))))] (binding [*api-context* api-context#] ~@body))) @@ -43,6 +43,11 @@ (throw (IllegalArgumentException. ~(str "The parameter \"" p "\" is required")))))) (list* 'do))) +(defn with-collection-format + "Attach collection-format to meta data of the given parameter." + [param collection-format] + (and param (with-meta param {:collection-format collection-format}))) + (defn- ^SimpleDateFormat make-date-format ([^String format-str] (make-date-format format-str nil)) ([^String format-str ^String time-zone] @@ -119,23 +124,40 @@ [auth-names] (reduce process-auth {} auth-names)) +(declare normalize-param) + (defn make-url "Make full URL by adding base URL and filling path parameters." [path path-params] (let [path (reduce (fn [p [k v]] - (str/replace p (re-pattern (str "\\{" k "\\}")) (param->str v))) + (str/replace p (re-pattern (str "\\{" k "\\}")) (normalize-param v))) path path-params)] (str (:base-url *api-context*) path))) +(defn normalize-array-param + "Normalize array paramater according to :collection-format specified in the parameter's meta data. + When the parameter contains File, a seq is returned so as to keep File parameters. + For :multi collection format, a seq is returned which will be handled properly by clj-http. + For other cases, a string is returned." + [xs] + (if (some (partial instance? File) xs) + (map normalize-param xs) + (case (-> (meta xs) :collection-format (or :csv)) + :csv (str/join "," (map normalize-param xs)) + :ssv (str/join " " (map normalize-param xs)) + :tsv (str/join "\t" (map normalize-param xs)) + :pipes (str/join "|" (map normalize-param xs)) + :multi (map normalize-param xs)))) + (defn normalize-param "Normalize parameter value, handling three cases: - for sequential value, normalize each elements of it; - for File value, do nothing with it; - otherwise, call `param->str`." + for sequential value, apply `normalize-array-param` which handles collection format; + for File value, use current value; + otherwise, apply `param->str`." [param] (cond - (sequential? param) (map normalize-param param) + (sequential? param) (normalize-array-param param) (instance? File param) param :else (param->str param))) diff --git a/samples/client/petstore/clojure/src/swagger_petstore/api/pet.clj b/samples/client/petstore/clojure/src/swagger_petstore/api/pet.clj index ac4b921ba1e..8cf64f607b2 100644 --- a/samples/client/petstore/clojure/src/swagger_petstore/api/pet.clj +++ b/samples/client/petstore/clojure/src/swagger_petstore/api/pet.clj @@ -1,5 +1,5 @@ (ns swagger-petstore.api.pet - (:require [swagger-petstore.core :refer [call-api check-required-params]]) + (:require [swagger-petstore.core :refer [call-api check-required-params with-collection-format]]) (:import (java.io File))) (defn update-pet @@ -40,7 +40,7 @@ (call-api "/pet/findByStatus" :get {:path-params {} :header-params {} - :query-params {"status" status } + :query-params {"status" (with-collection-format status :multi) } :form-params {} :content-types [] :accepts ["application/json" "application/xml"] @@ -54,7 +54,7 @@ (call-api "/pet/findByTags" :get {:path-params {} :header-params {} - :query-params {"tags" tags } + :query-params {"tags" (with-collection-format tags :multi) } :form-params {} :content-types [] :accepts ["application/json" "application/xml"] diff --git a/samples/client/petstore/clojure/src/swagger_petstore/api/store.clj b/samples/client/petstore/clojure/src/swagger_petstore/api/store.clj index 054b3671fa6..d3b0d9f5abe 100644 --- a/samples/client/petstore/clojure/src/swagger_petstore/api/store.clj +++ b/samples/client/petstore/clojure/src/swagger_petstore/api/store.clj @@ -1,5 +1,5 @@ (ns swagger-petstore.api.store - (:require [swagger-petstore.core :refer [call-api check-required-params]]) + (:require [swagger-petstore.core :refer [call-api check-required-params with-collection-format]]) (:import (java.io File))) (defn get-inventory diff --git a/samples/client/petstore/clojure/src/swagger_petstore/api/user.clj b/samples/client/petstore/clojure/src/swagger_petstore/api/user.clj index 2e5c5782e04..64ad38627ed 100644 --- a/samples/client/petstore/clojure/src/swagger_petstore/api/user.clj +++ b/samples/client/petstore/clojure/src/swagger_petstore/api/user.clj @@ -1,5 +1,5 @@ (ns swagger-petstore.api.user - (:require [swagger-petstore.core :refer [call-api check-required-params]]) + (:require [swagger-petstore.core :refer [call-api check-required-params with-collection-format]]) (:import (java.io File))) (defn create-user diff --git a/samples/client/petstore/clojure/src/swagger_petstore/core.clj b/samples/client/petstore/clojure/src/swagger_petstore/core.clj index 50312c3d150..bcb9ed99ad2 100644 --- a/samples/client/petstore/clojure/src/swagger_petstore/core.clj +++ b/samples/client/petstore/clojure/src/swagger_petstore/core.clj @@ -29,8 +29,8 @@ [api-context & body] `(let [api-context# ~api-context api-context# (-> *api-context* - (merge api-context#) - (assoc :auths (merge (:auths *api-context*) (:auths api-context#))))] + (merge api-context#) + (assoc :auths (merge (:auths *api-context*) (:auths api-context#))))] (binding [*api-context* api-context#] ~@body))) @@ -43,6 +43,11 @@ (throw (IllegalArgumentException. ~(str "The parameter \"" p "\" is required")))))) (list* 'do))) +(defn with-collection-format + "Attach collection-format to meta data of the given parameter." + [param collection-format] + (and param (with-meta param {:collection-format collection-format}))) + (defn- ^SimpleDateFormat make-date-format ([^String format-str] (make-date-format format-str nil)) ([^String format-str ^String time-zone] @@ -119,23 +124,40 @@ [auth-names] (reduce process-auth {} auth-names)) +(declare normalize-param) + (defn make-url "Make full URL by adding base URL and filling path parameters." [path path-params] (let [path (reduce (fn [p [k v]] - (str/replace p (re-pattern (str "\\{" k "\\}")) (param->str v))) + (str/replace p (re-pattern (str "\\{" k "\\}")) (normalize-param v))) path path-params)] (str (:base-url *api-context*) path))) +(defn normalize-array-param + "Normalize array paramater according to :collection-format specified in the parameter's meta data. + When the parameter contains File, a seq is returned so as to keep File parameters. + For :multi collection format, a seq is returned which will be handled properly by clj-http. + For other cases, a string is returned." + [xs] + (if (some (partial instance? File) xs) + (map normalize-param xs) + (case (-> (meta xs) :collection-format (or :csv)) + :csv (str/join "," (map normalize-param xs)) + :ssv (str/join " " (map normalize-param xs)) + :tsv (str/join "\t" (map normalize-param xs)) + :pipes (str/join "|" (map normalize-param xs)) + :multi (map normalize-param xs)))) + (defn normalize-param "Normalize parameter value, handling three cases: - for sequential value, normalize each elements of it; - for File value, do nothing with it; - otherwise, call `param->str`." + for sequential value, apply `normalize-array-param` which handles collection format; + for File value, use current value; + otherwise, apply `param->str`." [param] (cond - (sequential? param) (map normalize-param param) + (sequential? param) (normalize-array-param param) (instance? File param) param :else (param->str param))) diff --git a/samples/client/petstore/clojure/test/swagger_petstore/core_test.clj b/samples/client/petstore/clojure/test/swagger_petstore/core_test.clj index b68358a860e..27a9e1db291 100644 --- a/samples/client/petstore/clojure/test/swagger_petstore/core_test.clj +++ b/samples/client/petstore/clojure/test/swagger_petstore/core_test.clj @@ -124,14 +124,27 @@ (let [file (-> "hello.txt" io/resource io/file)] (are [param expected] (is (= expected (normalize-param param))) - [12 "34"] ["12" "34"] file file "abc" "abc" - [[12 "34"] file "abc"] [["12" "34"] file "abc"]))) + [12 "34"] "12,34" + ^{:collection-format :csv} [12 "34"] "12,34" + ^{:collection-format :ssv} [12 "34"] "12 34" + ^{:collection-format :tsv} [12 "34"] "12\t34" + (with-collection-format [12 "34"] :pipes) "12|34" + (with-collection-format [12 "34"] :multi) ["12" "34"] + [[12 "34"] file "abc"] ["12,34" file "abc"]))) (deftest test-normalize-params - (is (= {:a "123" :b ["4" ["5" "6"]]} - (normalize-params {:a 123 :b [4 [5 "6"]] :c nil})))) + (is (= {:a "123" :b "4,5,6"} + (normalize-params {:a 123 :b [4 [5 "6"]] :c nil}))) + (is (= {:a "123" :b ["4" "5,6"]} + (normalize-params {:a 123 + :b ^{:collection-format :multi} [4 [5 "6"]] + :c nil}))) + (is (= {:a "123" :b "4 5|6"} + (normalize-params {:a 123 + :b (with-collection-format [4 (with-collection-format [5 "6"] :pipes)] :ssv) + :c nil})))) (deftest test-json-mime? (are [mime expected]