Skip to content

Commit

Permalink
Merge pull request #217 from metosin/swagger-routes
Browse files Browse the repository at this point in the history
Swagger routes
  • Loading branch information
ikitommi committed Feb 2, 2016
2 parents ede30f1 + be25179 commit eded74c
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 137 deletions.
25 changes: 22 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,26 @@
* `context*` => `context`
* `defroutes*` => `defroutes`

* **BREAKING** `public-resource-routes` & `public-resources` are removed from `compojure.api.middleware`.
* **BREAKING** `swagger-docs` and `swagger-ui` are now longer in `compojure.api.sweet`
* Syntax was hairy and when configuring the spec-url it needed to be set to both in order to work
* In future, there are multiple ways of setting the swagger stuff:
* via api-options `:swagger` (has no defaults)
* via `swagger-routes` function, mounting both the `swagger-ui` and `swagger-docs` and wiring them together
* by default, mounts the swagger-ui to `/` and the swagger-spec to `/swagger.json`
* via the old `swagger-ui` & `swagger-docs` (need to be separately imported from `compojure.api.swagger`).
* see https://github.com/metosin/compojure-api/wiki/Swagger-integration for details

```clj
(defapi app
(swagger-routes)
(GET "/ping" []
(ok {:message "pong"})))

(defapi app
{:swagger {:ui "/", :spec "/swagger.json"}}
(GET "/ping" []
(ok {:message "pong"})))
```

* **BREAKING**: api-level coercion option is now a function of `request => type => matcher` as it is documented.
Previously required a `type => matcher` map. Options are checked against `type => matcher` coercion input, and a
Expand All @@ -70,6 +89,8 @@ take a vector of middleware containing either
has been renamed to `:swagger`.
* will break at macro-expansion time with helpful exception

* **BREAKING** `public-resource-routes` & `public-resources` are removed from `compojure.api.middleware`.

* **BREAKING**: `compojure.api.legacy` namespace has been removed.

### Migration guide
Expand All @@ -84,8 +105,6 @@ https://github.com/metosin/compojure-api/wiki/Migration-Guide-to-1.0.0

* top-level `api` is now just function, not a macro. It takes an optional options maps and a top-level route function.

* `swagger-docs` and `swagger-ui` are now functions instead of macros.

* Coercer cache is now at api-level with 10000 entries.

* Code generated from restructured route macros is much cleaner now
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ Stuff on top of [Compojure](https://github.com/weavejester/compojure) for making
:city s/Str}})

(defapi app
(swagger-ui)
(swagger-docs
{:info {:title "My Swagger API"
:description "Compojure Api example"}
:tags [{:name "api", :description "sample api"}]})
{:swagger {:spec "/swagger.json"
:ui "/api-docs"
:data {:data {:info {:title "My Swagger API"
:description "Compojure Api example"}
:tags [{:name "api", :description "sample api"}]}})
(context "/api" []
:tags ["api"]
(GET "/hello" []
Expand Down
42 changes: 21 additions & 21 deletions examples/src/examples/thingie.clj
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,27 @@

(def app
(api
(swagger-ui)
(swagger-docs
{:info {:version "1.0.0"
:title "Thingies API"
:description "the description"
:termsOfService "http://www.metosin.fi"
:contact {:name "My API Team"
:email "[email protected]"
:url "http://www.metosin.fi"}
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}}
:tags [{:name "math", :description "Math with parameters"}
{:name "pizzas", :description "Pizza API"}
{:name "failing", :description "Handling uncaught exceptions"}
{:name "dates", :description "Dates API"}
{:name "responses", :description "Responses demo"}
{:name "primitives", :description "Returning primitive values"}
{:name "context", :description "context routes"}
{:name "echo", :description "Echoes data"}
{:name "ordered", :description "Ordered routes"}
{:name "file", :description "File upload"}]})
{:swagger {:ui "/"
:spec "/swagger.json"
:data {:info {:version "1.0.0"
:title "Thingies API"
:description "the description"
:termsOfService "http://www.metosin.fi"
:contact {:name "My API Team"
:email "[email protected]"
:url "http://www.metosin.fi"}
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}}
:tags [{:name "math", :description "Math with parameters"}
{:name "pizzas", :description "Pizza API"}
{:name "failing", :description "Handling uncaught exceptions"}
{:name "dates", :description "Dates API"}
{:name "responses", :description "Responses demo"}
{:name "primitives", :description "Returning primitive values"}
{:name "context", :description "context routes"}
{:name "echo", :description "Echoes data"}
{:name "ordered", :description "Ordered routes"}
{:name "file", :description "File upload"}]}}}

(context "/math" []
:tags ["math"]
Expand Down
22 changes: 14 additions & 8 deletions src/compojure/api/api.clj
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
(ns compojure.api.api
(:require [compojure.api.core :as core]
(:require [compojure.api.core :as c]
[compojure.api.swagger :as swagger]
[compojure.api.middleware :as middleware]
[compojure.api.routes :as routes]
[compojure.api.common :as common]
[ring.swagger.common :as rsc]
[compojure.api.meta :as meta]))
[compojure.api.meta :as meta]
[ring.swagger.middleware :as rsm]))

(def api-defaults
(merge
middleware/api-middleware-defaults
{:api {:invalid-routes-fn routes/log-invalid-child-routes}}))
{:api {:invalid-routes-fn routes/log-invalid-child-routes}
:swagger nil}))

(defn
^{:doc (str
Expand All @@ -31,23 +33,27 @@
invalid routes (not satisfying compojure.api.route.Routing)
setting value to nil ignores invalid routes completely.
defaults to `compojure.api.routes/log-invalid-child-routes`
- **:swagger** Options to configure the Swagger-routes. Defaults to nil.
See `compojure.api.swagger/swagger-routes` for details.
### api-middleware options
" (:doc (meta #'compojure.api.middleware/api-middleware)))}
api
[& body]
(let [[options handlers] (common/extract-parameters body)
(let [[options handlers] (common/extract-parameters body false)
options (rsc/deep-merge api-defaults options)
handler (apply core/routes handlers)
handler (apply c/routes (concat handlers [(swagger/swagger-routes (:swagger options))]))
routes (routes/get-routes handler (:api options))
paths (-> routes routes/ring-swagger-paths swagger/transform-operations)
lookup (routes/route-lookup-table routes)
swagger-data (get-in options [:swagger :data])
api-handler (-> handler
(middleware/api-middleware (dissoc options :api))
(cond-> swagger-data (rsm/wrap-swagger-data swagger-data))
(middleware/api-middleware (dissoc options :api :swagger))
(middleware/wrap-options {:paths paths
:coercer (meta/memoized-coercer)
:lookup lookup}))]
:coercer (meta/memoized-coercer)
:lookup lookup}))]
(routes/create nil nil {} [handler] api-handler)))

(defmacro
Expand Down
4 changes: 2 additions & 2 deletions src/compojure/api/common.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
3. else => `{}`
Returns a tuple with parameters and body without the parameters"
[c]
[c expect-body]
(cond
(plain-map? (first c))
(and (plain-map? (first c)) (or (not expect-body) (seq (rest c))))
[(first c) (seq (rest c))]

(keyword? (first c))
Expand Down
2 changes: 1 addition & 1 deletion src/compojure/api/meta.clj
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@
(seq responses) (assoc :responses (apply merge responses))))

(defn restructure [method [path arg & args] {:keys [context?]}]
(let [[options body] (extract-parameters args)
(let [[options body] (extract-parameters args true)
[path-string lets arg-with-request arg] (destructure-compojure-api-request path arg)

{:keys [lets
Expand Down
110 changes: 63 additions & 47 deletions src/compojure/api/swagger.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
(ns compojure.api.swagger
(:require [compojure.api.common :refer :all]
[compojure.api.core :refer [GET undocumented]]
[compojure.api.common :refer [extract-parameters]]
(:require [compojure.api.core :as c]
[compojure.api.common :as common]
[compojure.api.middleware :as mw]
[ring.util.http-response :refer [ok]]
[ring.swagger.common :as rsc]
Expand All @@ -13,10 +12,6 @@
[compojure.api.routes :as routes]
[cheshire.core :as cheshire]))

;;
;; generate schema names
;;

#_(defn ensure-parameter-schema-names [endpoint]
(if (get-in endpoint [:parameters :body])
(update-in endpoint [:parameters :body] #(swagger/with-named-sub-schemas % "Body"))
Expand All @@ -33,51 +28,34 @@
responses))))
endpoint))

;;
;; routes
;;
(defn base-path [request]
(let [context (swagger/context request)]
(if (= "" context) "/" context)))

(defn swagger-spec-path
[app]
(some-> app
routes/get-routes
routes/route-lookup-table
::swagger
keys
first))

(defn transform-operations [swagger]
(->> swagger
(swagger2/transform-operations routes/non-nil-routes)
(swagger2/transform-operations routes/strip-no-doc-endpoints)))

(defn base-path [request]
(let [context (swagger/context request)]
(if (= "" context) "/" context)))

;;
;; Public api
;;

(defn swagger-ui [& params]
(undocumented
(c/undocumented
(apply rsui/swagger-ui params)))

(defn swagger-docs
"Route to serve the swagger api-docs. If the first
parameter is a String, it is used as a url for the
api-docs, otherwise \"/swagger.json\" will be used.
Next Keyword value pairs OR a map for meta-data.
Meta-data can be any valid swagger 2.0 data. Common
case is to introduce API Info and Tags here:
{:info {:version \"1.0.0\"
:title \"Sausages\"
:description \"Sausage description\"
:termsOfService \"http://helloreverb.com/terms/\"
:contact {:name \"My API Team\"
:email \"[email protected]\"
:url \"http://www.metosin.fi\"}
:license {:name: \"Eclipse Public License\"
:url: \"http://www.eclipse.org/legal/epl-v10.html\"}}
:tags [{:name \"sausages\", :description \"Sausage api-set}]}"
[& body]
(defn swagger-docs [& body]
(let [[path body] (if (string? (first body))
[(first body) (rest body)]
["/swagger.json" body])
[extra-info] (extract-parameters body)]
(GET path request
[extra-info] (common/extract-parameters body false)]
(c/GET path request
:no-doc true
:name ::swagger
(let [runtime-info (rsm/get-swagger-data request)
Expand All @@ -88,13 +66,51 @@
spec (swagger2/swagger-json swagger options)]
(ok spec)))))

(defn swagger-spec-path [app]
(some-> app
routes/get-routes
routes/route-lookup-table
::swagger
keys
first))
;;
;; Public api
;;

(def swagger-defaults {:ui "/", :spec "/swagger.json"})

(defn swagger-routes
"Returns routes for swagger-articats (ui & spec). Accepts an options map, with the
following options:
**:ui** Uri for the swagger-ui (defaults to \"/\").
Setting the value to nil will cause the swagger-ui not to be mounted
**:spec** Uri for the swagger-spec (defaults to \"/swagger.json\")
Setting the value to nil will cause the swagger-ui not to be mounted
**:data** Swagger data in the Ring-Swagger format.
**:options**
**:ui** Options to configure the ui
**:spec** Options to configure the spec. Nada at the moment.
Example options:
{:ui \"/api-docs\"
:spec \"/swagger.json\"
:options {:ui {:jsonEditor true}
:spec {}}
:data {:info {:version \"1.0.0\"
:title \"Sausages\"
:description \"Sausage description\"
:termsOfService \"http://helloreverb.com/terms/\"
:contact {:name \"My API Team\"
:email \"[email protected]\"
:url \"http://www.metosin.fi\"}
:license {:name: \"Eclipse Public License\"
:url: \"http://www.eclipse.org/legal/epl-v10.html\"}}
:tags [{:name \"sausages\", :description \"Sausage api-set\"}]}}"
([] (swagger-routes {}))
([options]
(if options
(let [{:keys [ui spec data] {ui-options :ui spec-options :spec} :options} (merge swagger-defaults options)]
(c/routes
(if ui (apply swagger-ui ui (mapcat identity (merge ui-options (if spec {:swagger-docs spec})))))
(if spec (apply swagger-docs spec (mapcat identity data))))))))

(defn validate
"Validates a api. If the api is Swagger-enabled, the swagger-spec
Expand Down
3 changes: 1 addition & 2 deletions src/compojure/api/sweet.clj
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@

[compojure.api.swagger

swagger-ui
swagger-docs]
swagger-routes]

[ring.swagger.json-schema

Expand Down
22 changes: 19 additions & 3 deletions test/compojure/api/common_test.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
(ns compojure.api.common-test
(:require [compojure.api.common :refer :all]
(:require [compojure.api.common :as common]
[midje.sweet :refer :all]))

(fact "group-with"
(group-with pos? [1 -10 2 -4 -1 999]) => [[1 2 999] [-10 -4 -1]]
(group-with pos? [1 2 999]) => [[1 2 999] nil])
(common/group-with pos? [1 -10 2 -4 -1 999]) => [[1 2 999] [-10 -4 -1]]
(common/group-with pos? [1 2 999]) => [[1 2 999] nil])

(fact "extract-parameters"

(facts "expect body"
(common/extract-parameters [] true) => [{} nil]
(common/extract-parameters [{:a 1}] true) => [{} [{:a 1}]]
(common/extract-parameters [:a 1] true) => [{:a 1} nil]
(common/extract-parameters [{:a 1} {:b 2}] true) => [{:a 1} [{:b 2}]]
(common/extract-parameters [:a 1 {:b 2}] true) => [{:a 1} [{:b 2}]])

(facts "don't expect body"
(common/extract-parameters [] false) => [{} nil]
(common/extract-parameters [{:a 1}] false) => [{:a 1} nil]
(common/extract-parameters [:a 1] false) => [{:a 1} nil]
(common/extract-parameters [{:a 1} {:b 2}] false) => [{:a 1} [{:b 2}]]
(common/extract-parameters [:a 1 {:b 2}] false) => [{:a 1} [{:b 2}]]))
Loading

0 comments on commit eded74c

Please sign in to comment.