Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swagger options identical to compojure-api #30

Merged
merged 5 commits into from
May 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
## 0.3.0-SNAPSHOT

* *BREAKING*: Removed type-level interceptors from ring-adapter.
* **BREAKING**: Removed type-level interceptors from ring-adapter.
* Support for Context-based urls, thanks to [Wout Neirynck](https://github.com/wneirynck).
* **BREAKING**: top-level swagger options are now in align to the compojure-api:
* Fixes [#22](https://github.com/metosin/kekkonen/issues/22)
* By default, `api`s don't bind swagger-spec & swagger-ui, use `:spec` & `:ui` options

### Old

```clj
{:swagger {:info {:title "Kekkonen"}}
:swagger-ui {:jsonEdit true}})
```

### New

```clj
{:swagger
{:spec "/swagger.json"
:ui "/api-docs"
:options {:ui {:jsonEdit true}
:spec {:ignore-missing-mappings? false}}
:data {:info {:title "Kekkonen"}}}}
```

* updated dependencies:

Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Mostly written as [issues](https://github.com/metosin/kekkonen/issues). Biggest
(success {:result (+ x y)}))

(defnk ^:command inc!
[[:components counter]]
[counter]
(success {:result (swap! counter inc)}))

;;
Expand All @@ -146,10 +146,12 @@ Mostly written as [issues](https://github.com/metosin/kekkonen/issues). Biggest

(def app
(cqrs-api
{:swagger {:info {:title "Kekkonen example"}}
{:swagger {:ui "/api-docs"
:spec "/swagger.json"
:data {:info {:title "Kekkonen example"}}}
:core {:handlers {:api {:pizza #'echo-pizza
:example [#'ping #'inc! #'plus]}}
:context {:components {:counter (atom 0)}}}}))
:context {:counter (atom 0)}}}))

;;
;; Start it
Expand Down
4 changes: 3 additions & 1 deletion dev-src/example/api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@

(def app
(cqrs-api
{:swagger {:info {:title "Kekkonen example"}}
{:swagger {:ui "/api-docs"
:spec "/swagger.json"
:data {:info {:title "Kekkonen example"}}}
:core {:handlers {:api {:pizza #'echo-pizza
:sample [#'ping #'inc! #'plus]}}
:context {:components {:counter (atom 0)}}}}))
Expand Down
4 changes: 3 additions & 1 deletion dev-src/example/cqrs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@

(def app
(cqrs-api
{:swagger {:info {:title "Kekkonen"}}
{:swagger {:ui "/api-docs"
:spec "/swagger.json"
:data {:info {:title "Kekkonen"}}}
:core {:handlers {:api {:item [#'get-items #'add-item #'reset-items]
:calculator [#'plus #'times #'increment]
:security #'get-user
Expand Down
6 changes: 4 additions & 2 deletions dev-src/example/github/github.clj
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@

(def app
(cqrs-api
{:swagger {:info {:title "Kekkonen"
:version "1.0"}}
{:swagger {:ui "/api-docs"
:spec "/swagger.json"
:data {:info {:title "Kekkonen"
:version "1.0"}}}
:core {:handlers {:api {:github [#'get-repository
#'list-repositorys
#'fork
Expand Down
6 changes: 4 additions & 2 deletions examples/component/src/sample/handler.clj
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@

(p/defnk create [[:state counter]]
(cqrs-api
{:swagger {:info {:title "Kekkonen with Component"
:description "created with http://kekkonen.io"}}
{:swagger {:ui "/api-docs"
:spec "/swagger.json"
:data {:info {:title "Kekkonen with Component"
:description "created with http://kekkonen.io"}}}
:core {:handlers {:api {:pizza #'echo-pizza
:math [#'inc! #'plus]
:ping #'ping}}
Expand Down
20 changes: 11 additions & 9 deletions src/kekkonen/api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
(s/optional-key :api) {:handlers k/KeywordMap}
(s/optional-key :ring) r/Options
(s/optional-key :mw) k/KeywordMap
(s/optional-key :swagger) k/KeywordMap
(s/optional-key :swagger-ui) k/KeywordMap})
(s/optional-key :swagger) ks/Options})

(s/def +default-options+ :- Options
{:core (-> k/+default-options+
Expand All @@ -21,18 +20,21 @@
:api {:handlers r/+kekkonen-handlers+}
:ring r/+default-options+
:mw mw/+default-options+
:swagger {:info {:title "Kekkonen API"}}
:swagger-ui ks/+default-swagger-ui-options+})
:swagger {:data {:info {:title "Kekkonen API"
:version "0.0.1"}}}})

(defn api [options]
(s/with-fn-validation
(let [options (s/validate Options (kc/deep-merge-map-like +default-options+ options))
swagger (merge (:swagger options) (mw/api-info (:mw options)))
dispatcher (-> (k/dispatcher (:core options))
(k/inject (-> options :api :handlers))
(k/inject (ks/swagger-handler swagger options)))]
api-handlers (-> options :api :handlers)
swagger-data (merge (-> options :swagger :data) (mw/api-info (:mw options)))
swagger-options (-> options :swagger)
swagger-handler (ks/swagger-handler swagger-data swagger-options)
dispatcher (cond-> (k/dispatcher (:core options))
api-handlers (k/inject api-handlers)
swagger-handler (k/inject swagger-handler))]
(mw/wrap-api
(r/routes
[(r/ring-handler dispatcher (:ring options))
(ks/swagger-ui (:swagger-ui options))])
(ks/swagger-ui swagger-options)])
(:mw options)))))
3 changes: 2 additions & 1 deletion src/kekkonen/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@
:no-doc nil
;; TODO: should this be defined in kekkonen.ring?
:responses nil}})

(s/defn dispatcher :- Dispatcher
"Creates a Dispatcher"
[options :- Options]
Expand All @@ -618,7 +619,7 @@

(s/defn inject
"Injects handlers into an existing Dispatcher"
[dispatcher :- Dispatcher, handlers]
[dispatcher :- Dispatcher, handlers :- (s/constrained s/Any (complement nil?) 'not-nil)]
(if handlers
(let [handler (collect-and-enrich
(merge dispatcher {:handlers handlers :type-resolver any-type-resolver}) true)]
Expand Down
2 changes: 1 addition & 1 deletion src/kekkonen/cqrs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
(ka/api
(kc/deep-merge-map-like
{:core {:type-resolver (k/type-resolver :command :query)}
:swagger {:info {:title "Kekkonen CQRS API"}}
:swagger {:data {:info {:title "Kekkonen CQRS API"}}}
:ring {:types {:query {:methods #{:get}
:parameters {[:data] [:request :query-params]}}
:command {:methods #{:post}
Expand Down
2 changes: 1 addition & 1 deletion src/kekkonen/http.clj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
(ka/api
(kc/deep-merge-map-like
{:core {:type-resolver (k/type-resolver :get :head :patch :delete :options :post :put :any)}
:swagger {:info {:title "Kekkonen HTTP API"}}
:swagger {:data {:info {:title "Kekkonen HTTP API"}}}
:ring {:types {:get {:methods #{:get}}
:head {:methods #{:head}}
:patch {:methods #{:patch}}
Expand Down
4 changes: 2 additions & 2 deletions src/kekkonen/ring.clj
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@

(s/defn routes :- k/Function
"Creates a ring handler of multiples handlers, matches in order."
[ring-handlers :- [k/Function]]
(apply some-fn ring-handlers))
[ring-handlers :- [(s/maybe k/Function)]]
(apply some-fn (keep identity ring-handlers)))

(s/defn match
"Creates a ring-handler for given uri & request-method"
Expand Down
47 changes: 29 additions & 18 deletions src/kekkonen/swagger.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
[kekkonen.core :as k]
[kekkonen.common :as kc]
[plumbing.core :as p]
[kekkonen.ring :as r]))
[kekkonen.ring :as r]
[clojure.string :as str]))

(def +default-swagger-ui-options+
{:path "/"})
(s/defschema Options
{(s/optional-key :ui) (s/maybe s/Str)
(s/optional-key :spec) (s/maybe s/Str)
(s/optional-key :data) k/KeywordMap
(s/optional-key :options) {(s/optional-key :ui) k/KeywordMap
(s/optional-key :spec) k/KeywordMap}})

(defn transform-handler
"Transforms a handler into ring-swagger path->method->operation map."
Expand Down Expand Up @@ -47,8 +52,11 @@

(s/defn swagger-ui
"Ring handler for the Swagger UI"
[options]
(apply ui/swagger-ui (into [(:path options)] (apply concat (dissoc options :path)))))
[{:keys [ui spec] :as options}]
(when ui
(apply ui/swagger-ui (into [ui] (apply concat (merge
{:swagger-docs spec}
(-> options :options :ui)))))))

(defn- add-base-path
"Extracts the base path from the context and adds it to the swagger map as basePath"
Expand All @@ -57,16 +65,19 @@
(assoc swagger :basePath context)
swagger))

(defn swagger-handler [swagger options]
(k/handler
{:type :kekkonen.ring/handler
:kekkonen.ring/method :get
:name "swagger.json"
:no-doc true}
(fn [{:keys [request] :as context}]
(let [dispatcher (k/get-dispatcher context)
ns (some-> context :request :query-params :ns str keyword)
handlers (k/available-handlers dispatcher ns (#'r/clean-context context))]
(ok (swagger-object
(add-base-path request (ring-swagger handlers swagger))
options))))))
(defn swagger-handler
"Creates a handler, that serves the swagger-spec"
[swagger options]
(if-let [spec (:spec options)]
(k/handler
{:type :kekkonen.ring/handler
:kekkonen.ring/method :get
:name spec
:no-doc true}
(fn [{:keys [request] :as context}]
(let [dispatcher (k/get-dispatcher context)
ns (some-> context :request :query-params :ns str keyword)
handlers (k/available-handlers dispatcher ns (#'r/clean-context context))]
(ok (swagger-object
(add-base-path request (ring-swagger handlers swagger))
(-> options :options :spec))))))))
23 changes: 20 additions & 3 deletions test/kekkonen/api_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
context)))

(facts "api-test"
(let [app (api {:core {:handlers {:api {:public [#'plus #'nada]
(let [app (api {:swagger {:ui "/api-docs"
:spec "/swagger.json"}
:core {:handlers {:api {:public [#'plus #'nada]
secret-ns #'plus}}
:meta {::role require-role}}})]

Expand Down Expand Up @@ -294,12 +296,27 @@
{:/api/secret/plus anything})}))))

(fact "swagger-ui"
(let [response (app {:uri "/" :request-method :get})]
(let [response (app {:uri "/api-docs" :request-method :get})]
response => (contains
{:status 302
:body ""
:headers (contains
{"Location" "/index.html"})})))))
{"Location" "/api-docs/index.html"})})))))

(facts "swagger-options"

(fact "ui & spec are not set by default"
(let [app (api {:core {:handlers {:api #'plus}}})]

(app {:uri "/swagger.json", :request-method :get}) => not-found?
(app {:uri "/", :request-method :get}) => not-found?))

(fact "with ui & spec"
(let [app (api {:swagger {:spec "/swagger.json", :ui "/api-docs"}
:core {:handlers {:api #'plus}}})]

(app {:uri "/swagger.json", :request-method :get}) => ok?
(app {:uri "/api-docs/index.html", :request-method :get}) => ok?)))

(facts "api-meta"
(fact "meta can be presented as maps or vector of tuples"
Expand Down
22 changes: 14 additions & 8 deletions test/kekkonen/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -950,14 +950,20 @@
(fact "going meta boing boing"
(k/invoke d :api/names) => #{:dispatcher :handler :names})))

(fact "handlers can be injected into existing dispatcher"
(let [d (-> (k/dispatcher {:handlers {:api (k/handler {:name :test} identity)}})
(k/inject (k/handler {:name :ping} identity)))]
d => (contains
{:handlers
(just
{:api/test anything
:ping anything})})))
(fact "injecting handlers"

(fact "handlers can be injected"
(let [d (-> (k/dispatcher {:handlers {:api (k/handler {:name :test} identity)}})
(k/inject (k/handler {:name :ping} identity)))]
d => (contains
{:handlers
(just
{:api/test anything
:ping anything})})))

(fact "injecting nil handlers fails"
(-> (k/dispatcher {:handlers {:api (k/handler {:name :test} identity)}})
(k/inject nil)) => schema-error?))

(facts "coercion-matcher"
(let [PositiveInt (s/both s/Int (s/pred pos? 'positive))
Expand Down
2 changes: 2 additions & 0 deletions test/kekkonen/midje.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(ns kekkonen.midje
(:require [midje.util.exceptions :as e]
[kekkonen.core :as k]
[schema.core :as s]
[cheshire.core :as c]))

(defn throws?
Expand All @@ -14,6 +15,7 @@
(not (nil? x))
(= mdata m))))))

(def schema-error? (throws? {:type ::s/error}))
(def missing-route? (throws? {:type ::k/dispatch}))
(def input-coercion-error? (throws? {:type ::k/request}))
(def output-coercion-error? (throws? {:type ::k/response}))
Expand Down
2 changes: 1 addition & 1 deletion test/kekkonen/ring_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
(fact "request can be read as-is"
(let [request {:uri "/api/snoop" :request-method :post}]
(app request) => (ok request)))

(fact "handles request within context"
(let [request {:uri "/somecontext/api/ping" :request-method :post :context "/somecontext"}]
(app request) => "pong"))))
Expand Down
25 changes: 12 additions & 13 deletions test/kekkonen/swagger_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,21 @@

(fact "swagger-json can be generated"
(s/with-fn-validation
(ks/swagger-object swagger {}) => truthy))))
(ks/swagger-object swagger {}) => some?))))

(facts "swagger-handler"
(let [dispatcher (k/transform-handlers
(k/dispatcher {:handlers {:api {:admin #'echo}}})
(partial #'r/attach-ring-meta r/+default-options+))
ctx {}
h (ks/swagger-handler {} {:info {:version "1.2.3"}})]
swagger-handler (ks/swagger-handler {} {:spec "swagger.json", :info {:version "1.2.3"}})]

(against-background [(k/get-dispatcher anything) => dispatcher]

(fact "generates swagger json"
(:body (h ctx)) => (contains {:paths (as-checker not-empty)})))

(fact "extracts swagger basePath from request context"
(let [context-path "/testpath"]
(:body (h {:request {:context context-path}})) => (contains {:basePath context-path})))

(fact "does not add basePath if no context"
(-> (h {:request {}}) :body :basePath) => nil?)))
(fact "generates swagger json"
(swagger-handler {}) => (contains {:body (contains {:paths seq})})))

(fact "extracts swagger basePath from request context"
(let [context-path "/testpath"]
(:body (swagger-handler {:request {:context context-path}})) => (contains {:basePath context-path})))

(fact "does not add basePath if no context"
(-> (swagger-handler {:request {}}) :body :basePath) => nil)))