Skip to content

Commit 2902e69

Browse files
committed
Implement invalid route handling at api-option
* don't check at route creation time * optionally check at api-creation time * remove the *fail-on-missing-route-info* dynamic var
1 parent d93e9f0 commit 2902e69

File tree

10 files changed

+121
-54
lines changed

10 files changed

+121
-54
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929

3030
* **BREAKING** Vanilla Compojure routes will not produce any swagger-docs (as they do not satisfy the
3131
`Routing` protocol. They can still be used for handling request, just without docs.
32-
* **TODO**: a new api-level option to declare how to handle routes not satisfying the `Routing` protocol (fail, warn or ignore)
32+
* a new api-level option `[:api :invalid-routes-fn]` to declare how to handle routes not satisfying
33+
the `Routing` protocol. Default implementation logs invalid routes as WARNINGs.
3334

3435
* **BREAKING** compojure.core imports are removed from `compojure.api.sweet`:
3536
* `let-request`, `routing`, `wrap-routes`

src/compojure/api/api.clj

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,46 @@
55
[compojure.api.routes :as routes]
66
[compojure.api.common :as common]
77
[clojure.tools.macro :as macro]
8-
[ring.swagger.swagger2 :as swagger2]))
8+
[ring.swagger.common :as rsc]))
9+
10+
(def api-defaults
11+
(merge
12+
middleware/api-middleware-defaults
13+
{:api {:invalid-routes-fn routes/log-invalid-child-routes}}))
914

1015
(defn
1116
^{:doc (str
1217
"Returns a ring handler wrapped in compojure.api.middleware/api-middlware.
13-
Creates the route-table at run-time and passes that into the request via
14-
ring-swagger middlewares. The mounted api-middleware can be configured by
18+
Creates the route-table at run-time and injects that into the request via
19+
middlewares. Api and the mounted api-middleware can be configured by
1520
optional options map as the first parameter:
1621
1722
(api
1823
{:formats [:json :edn]}
1924
(context \"/api\" []
2025
...))
2126
22-
API middleware options:
27+
### direct api options:
28+
29+
- **:api** All api options are under `:api`.
30+
- **:invalid-routes-fn** A 2-arity function taking handler and a sequence of
31+
invalid routes (not satisfying compojure.api.route.Routing)
32+
setting value to nil ignores invalid routes completely.
33+
defaults to `compojure.api.routes/log-invalid-child-routes`
34+
35+
### api-middleware options
2336
2437
" (:doc (meta #'compojure.api.middleware/api-middleware)))}
2538
api
2639
[& body]
2740
(let [[options handlers] (common/extract-parameters body)
41+
options (rsc/deep-merge api-defaults options)
2842
handler (apply core/routes handlers)
29-
routes (routes/get-routes handler)
43+
routes (routes/get-routes handler (:api options))
3044
paths (-> routes routes/ring-swagger-paths swagger/transform-operations)
31-
lookup (routes/route-lookup-table handler)
45+
lookup (routes/route-lookup-table routes)
3246
api-handler (-> handler
33-
(middleware/api-middleware options)
47+
(middleware/api-middleware (dissoc options :api))
3448
(middleware/wrap-options {:paths paths
3549
:lookup lookup}))]
3650
(routes/create nil nil {} [handler] api-handler)))

src/compojure/api/common.clj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@
3434

3535
:else
3636
[{} (seq c)]))
37+
38+
(defn group-with
39+
"Groups a sequence with predicate returning a tuple of sequences."
40+
[pred coll]
41+
[(seq (filter pred coll))
42+
(seq (remove pred coll))])

src/compojure/api/routes.clj

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[cheshire.core :as json]
55
[compojure.api.middleware :as mw]
66
[compojure.api.impl.logging :as logging]
7+
[compojure.api.common :as common]
78
[ring.swagger.common :as rsc]
89
[clojure.string :as str]
910
[linked.core :as linked]
@@ -14,29 +15,40 @@
1415
;; Route records
1516
;;
1617

17-
(def ^:dynamic *fail-on-missing-route-info* false)
18-
1918
(defn- ->path [path]
2019
(if-not (= path "/") path))
2120

2221
(defn- ->paths [p1 p2]
2322
(->path (str p1 (->path p2))))
2423

2524
(defprotocol Routing
26-
(get-routes [handler]))
25+
(-get-routes [handler options]))
2726

2827
(extend-protocol Routing
2928
nil
30-
(get-routes [_] []))
29+
(-get-routes [_ _] []))
30+
31+
(defn filter-routes [{:keys [childs] :as handler} {:keys [invalid-routes-fn]}]
32+
(let [[valid-childs invalid-childs] (common/group-with (partial satisfies? Routing) childs)]
33+
(when (and invalid-routes-fn invalid-childs)
34+
(invalid-routes-fn handler invalid-childs))
35+
valid-childs))
36+
37+
(defn get-routes
38+
([handler]
39+
(get-routes handler nil))
40+
([handler options]
41+
(-get-routes handler options)))
3142

3243
(defrecord Route [path method info childs handler]
3344
Routing
34-
(get-routes [_]
35-
(if (seq childs)
36-
(vec
37-
(for [[p m i] (mapcat get-routes (filter (partial satisfies? Routing) childs))]
38-
[(->paths path p) m (rsc/deep-merge info i)]))
39-
(into [] (if path [[path method info]]))))
45+
(-get-routes [this options]
46+
(let [valid-childs (filter-routes this options)]
47+
(if (seq childs)
48+
(vec
49+
(for [[p m i] (mapcat #(get-routes % options) valid-childs)]
50+
[(->paths path p) m (rsc/deep-merge info i)]))
51+
(into [] (if path [[path method info]])))))
4052

4153
IFn
4254
(invoke [_ request]
@@ -45,17 +57,23 @@
4557
(AFn/applyToHelper this args)))
4658

4759
(defn create [path method info childs handler]
48-
(when-let [invalid-childs (seq (remove (partial satisfies? Routing) childs))]
49-
(let [message "Not all child routes satisfy compojure.api.routing/Routing."
50-
data {:path path
51-
:method method
52-
:info info
53-
:invalid invalid-childs}]
54-
(if *fail-on-missing-route-info*
55-
(throw (ex-info message data))
56-
(logging/log! :warn (str message ": " data)))))
5760
(->Route path method info childs handler))
5861

62+
;;
63+
;; Invalid route handlers
64+
;;
65+
66+
(defn fail-on-invalid-child-routes
67+
[handler invalid-childs]
68+
(throw (ex-info "Not all child routes satisfy compojure.api.routing/Routing."
69+
(merge (select-keys handler [:path :method]) {:invalid (vec invalid-childs)}))))
70+
71+
(defn log-invalid-child-routes [handler invalid-childs]
72+
(logging/log! :warn (str "Not all child routes satisfy compojure.api.routing/Routing. "
73+
(select-keys handler [:path :method]) ", invalid child routes: "
74+
(vec invalid-childs))))
75+
76+
5977
;;
6078
;; Swagger paths
6179
;;
@@ -79,14 +97,14 @@
7997
(defn ring-swagger-paths [routes]
8098
{:paths
8199
(reduce
82-
(fn [acc [path method info]]
83-
(update-in
84-
acc [path method]
85-
(fn [old-info]
86-
(let [info (or old-info info)]
87-
(ensure-path-parameters path info)))))
88-
(linked/map)
89-
routes)})
100+
(fn [acc [path method info]]
101+
(update-in
102+
acc [path method]
103+
(fn [old-info]
104+
(let [info (or old-info info)]
105+
(ensure-path-parameters path info)))))
106+
(linked/map)
107+
routes)})
90108

91109
;;
92110
;; Route lookup
@@ -96,8 +114,8 @@
96114
(for [[id freq] (frequencies seq)
97115
:when (> freq 1)] id))
98116

99-
(defn route-lookup-table [handler]
100-
(let [entries (for [[path endpoints] (-> handler get-routes ring-swagger-paths :paths)
117+
(defn route-lookup-table [routes]
118+
(let [entries (for [[path endpoints] (-> routes ring-swagger-paths :paths)
101119
[method {:keys [x-name parameters]}] endpoints
102120
:let [params (:path parameters)]
103121
:when x-name]

src/compojure/api/swagger.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090

9191
(defn swagger-spec-path [app]
9292
(some-> app
93+
routes/get-routes
9394
routes/route-lookup-table
9495
::swagger
9596
keys

test/compojure/api/common_test.clj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
(fact "map-of"
66
(let [a 1 b true c [:abba :jabba]]
77
(map-of a b c) => {:a 1 :b true :c [:abba :jabba]}))
8+
9+
(fact "group-with"
10+
(group-with pos? [1 -10 2 -4 -1 999]) => [[1 2 999] [-10 -4 -1]]
11+
(group-with pos? [1 2 999]) => [[1 2 999] nil])

test/compojure/api/integration_test.clj

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
[ring.swagger.core :as rsc]
1010
[ring.util.http-status :as status]
1111
[compojure.api.middleware :as mw]
12-
[ring.swagger.middleware :as rsm]))
12+
[ring.swagger.middleware :as rsm]
13+
[compojure.api.routes :as routes]))
1314

1415
;;
1516
;; Data
@@ -1106,3 +1107,19 @@
11061107
(let [[status body] (get* app "/ping")]
11071108
status => 200
11081109
body => 1))))
1110+
1111+
(fact "handling invalid routes with api"
1112+
(let [invalid-routes (routes (constantly nil))]
1113+
1114+
(fact "by default, logs the exception"
1115+
(api invalid-routes) => truthy
1116+
(provided
1117+
(compojure.api.impl.logging/log! :warn irrelevant) => irrelevant :times 1))
1118+
1119+
(fact "ignoring invalid routes doesn't log"
1120+
(api {:api {:invalid-routes-fn nil}} invalid-routes) => truthy
1121+
(provided
1122+
(compojure.api.impl.logging/log! :warn irrelevant) => irrelevant :times 0))
1123+
1124+
(fact "throwing exceptions"
1125+
(api {:api {:invalid-routes-fn routes/fail-on-invalid-child-routes}} invalid-routes)) => throws))

test/compojure/api/routes_test.clj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,19 @@
8686
(routes/get-routes (routes (routes))) => []
8787
(routes/get-routes (routes (swagger-ui))) => []
8888
(routes/get-routes (routes (routes (GET "/ping" [] "pong")))) => [["/ping" :get {}]])
89+
90+
(fact "invalid route options"
91+
(let [r (routes (constantly nil))]
92+
93+
(fact "ignore 'em all"
94+
(routes/get-routes r) => []
95+
(routes/get-routes r nil) => []
96+
(routes/get-routes r {:invalid-routes-fn nil}) => [])
97+
98+
(fact "log warnings"
99+
(routes/get-routes r {:invalid-routes-fn routes/log-invalid-child-routes}) => []
100+
(provided
101+
(compojure.api.impl.logging/log! :warn irrelevant) => irrelevant :times 1))
102+
103+
(fact "throw exception"
104+
(routes/get-routes r {:invalid-routes-fn routes/fail-on-invalid-child-routes})) => throws))

test/compojure/api/swagger_test.clj

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,12 @@
5555
(GET+ "/true" [] identity))) => {"/api/xxx/true" {:get {}}})
5656

5757
(fact "Vanilla Compojure defroutes are NOT followed"
58-
(ignore-non-documented-route-warning
59-
(compojure.core/defroutes even-more-routes (GET "/even" [] identity))
60-
(compojure.core/defroutes more-routes (context "/more" [] even-more-routes))
61-
(extract-paths
62-
(context "/api" []
63-
(GET "/true" [] identity)
64-
more-routes)) => {"/api/true" {:get {}}}))
58+
(compojure.core/defroutes even-more-routes (GET "/even" [] identity))
59+
(compojure.core/defroutes more-routes (context "/more" [] even-more-routes))
60+
(extract-paths
61+
(context "/api" []
62+
(GET "/true" [] identity)
63+
more-routes)) => {"/api/true" {:get {}}})
6564

6665
(fact "Compojure Api defroutes and def routes are followed"
6766
(def even-more-routes (GET "/even" [] identity))

test/compojure/api/test_utils.clj

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,6 @@
8080
(let [[status body] (raw-post* app uri "" nil headers)]
8181
[status (parse-body body)]))
8282

83-
;;
84-
;; Route compilation
85-
;;
86-
87-
(defmacro ignore-non-documented-route-warning [& body]
88-
`(with-out-str
89-
(binding [routes/*fail-on-missing-route-info* false]
90-
~@body)))
91-
9283
;;
9384
;; get-spec
9485
;;

0 commit comments

Comments
 (0)