From efd5a71ad9cdcc53e9808b043c585644c90c7681 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 24 Apr 2024 00:09:45 -0500 Subject: [PATCH] :headers double --- CHANGELOG.md | 6 +- src/compojure/api/meta.clj | 14 +++- test/compojure/api/meta_test.clj | 138 +++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2159b28b..eebea33c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ See also: [compojure-api 1.1.x changelog](./CHANGELOG-1.1.x.md) ## NEXT -* Throw an error on malformed `:{body,query}`, in particular is anything other than 2 elements was provided - * Disable check with `-Dcompojure.api.meta.allow-bad-{body,query}=true` -* 50% reduction in the number of times `:{return,body,query,responses}` schemas are evaluated +* Throw an error on malformed `:{body,query,headers}`, in particular is anything other than 2 elements was provided + * Disable check with `-Dcompojure.api.meta.allow-bad-{body,query,headers}=true` +* 50% reduction in the number of times `:{return,body,query,responses,headers}` schemas are evaluated * saves 1 evaluation for static contexts * saves 1 evaluation per request for dynamic contexts diff --git a/src/compojure/api/meta.clj b/src/compojure/api/meta.clj index 424260b5..39b8f2eb 100644 --- a/src/compojure/api/meta.clj +++ b/src/compojure/api/meta.clj @@ -390,10 +390,16 @@ " :headers [headers HeaderSchema]" " (ok headers))"))) -(defmethod restructure-param :headers [_ [value schema] acc] - (-> acc - (update-in [:lets] into [value (src-coerce! schema :headers :string)]) - (assoc-in [:info :public :parameters :header] schema))) +(defmethod restructure-param :headers [_ [value schema :as bv] acc] + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-headers")) + (assert (= 2 (count bv)) + (str ":headers should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-headers=true"))) + (let [g (gensym 'headers-schema)] + (-> acc + (update :outer-lets into [g schema]) + (update-in [:lets] into [value (src-coerce! g :headers :string)]) + (assoc-in [:info :public :parameters :header] g)))) ;; ;; body-params diff --git a/test/compojure/api/meta_test.clj b/test/compojure/api/meta_test.clj index 5c044076..3775172d 100644 --- a/test/compojure/api/meta_test.clj +++ b/test/compojure/api/meta_test.clj @@ -861,3 +861,141 @@ (is (= 1 @times)) (dorun (repeatedly 10 exercise)) (is (= 1 @times))))) + +(deftest headers-double-eval-test + (testing "no :headers double expansion" + (is-expands (GET "/ping" [] + :headers [headers EXPENSIVE] + (ok "kikka")) + `(let [?headers-schema ~'EXPENSIVE] + (map->Route + {:path "/ping", + :method :get, + :info (merge-parameters + {:public {:parameters {:header ?headers-schema}}}) + :handler + (make-route + :get + {:__record__ "clout.core.CompiledRoute", + :source "/ping", + :re #"/ping", + :keys [], + :absolute? false} + (fn [?request] + (let-request [[:as +compojure-api-request+] ?request] + (let [~'headers (compojure.api.coercion/coerce-request! + ?headers-schema + :headers + :string + true + false + +compojure-api-request+)] + (do ~'(ok "kikka"))))))})))) + (testing "no context" + (let [times (atom 0) + route (GET "/ping" [] + :headers [body (do (swap! times inc) s/Any)] + (ok "kikka")) + exercise #(is (= "kikka" (:body (route {:request-method :get :uri "/ping"}))))] + (exercise) + (is (= 1 @times)) + (dorun (repeatedly 10 exercise)) + (is (= 1 @times)))) + (testing "inferred static context" + (let [times (atom 0) + route (context + "" [] + (GET "/ping" [] + :headers [body (do (swap! times inc) s/Any)] + (ok "kikka"))) + exercise #(is (= "kikka" (:body (route {:request-method :get :uri "/ping"}))))] + (exercise) + (is (= 1 @times)) + (dorun (repeatedly 10 exercise)) + (is (= 1 @times)))) + (testing "dynamic context that doesn't bind variables" + (let [times (atom 0) + route (context + "" [] + :dynamic true + (GET "/ping" [] + :headers [body (do (swap! times inc) s/Any)] + (ok "kikka"))) + exercise #(is (= "kikka" (:body (route {:request-method :get :uri "/ping"}))))] + (exercise) + (is (= 1 @times)) + (dorun (repeatedly 10 exercise)) + (is (= 11 @times)))) + (testing "dynamic context where schema is bound outside context" + (let [times (atom 0) + route (let [s s/Any] + (context + "" [] + :dynamic true + (GET "/ping" [] + ;;TODO could lift this since the locals occur outside the context + :headers [body (do (swap! times inc) s)] + (ok "kikka")))) + exercise #(is (= "kikka" (:body (route {:request-method :get :uri "/ping"}))))] + (exercise) + (is (= 1 @times)) + (dorun (repeatedly 10 exercise)) + (is (= 11 @times)))) + (testing "dynamic context that binds req and uses it in schema" + (let [times (atom 0) + route (context + "" req + (GET "/ping" req + :headers [body (do (swap! times inc) + ;; should never lift this since it refers to request + (second [req s/Any]))] + (ok "kikka"))) + exercise #(is (= "kikka" (:body (route {:request-method :get :uri "/ping"}))))] + (exercise) + (is (= 1 @times)) + (dorun (repeatedly 10 exercise)) + (is (= 11 @times)))) + (testing "bind :headers in static context" + (is-thrown-with-msg? + AssertionError + #"cannot be :static" + (eval `(context + "" [] + :static true + :headers [body (do (swap! times update :outer inc) + s/Any)] + (GET "/ping" req + :headers [body (do (swap! times update :inner inc) + s/Any)] + (ok "kikka")))))) + (testing "bind :headers in dynamic context" + (let [times (atom {:outer 0 :inner 0}) + route (context + "" [] + :dynamic true + :headers [body (do (swap! times update :outer inc) + s/Any)] + (GET "/ping" req + :headers [body (do (swap! times update :inner inc) + s/Any)] + (ok "kikka"))) + exercise #(is (= "kikka" (:body (route {:request-method :get :uri "/ping"}))))] + (exercise) + (is (= {:outer 1 :inner 1} @times)) + (dorun (repeatedly 10 exercise)) + (is (= {:outer 1 :inner 11} @times)))) + (testing "idea for lifting impl" + (let [times (atom 0) + route (let [rs (GET "/ping" req + :headers [body (do (swap! times inc) + s/Any)] + (ok "kikka"))] + (context + "" [] + :dynamic true + rs)) + exercise #(is (= "kikka" (:body (route {:request-method :get :uri "/ping"}))))] + (exercise) + (is (= 1 @times)) + (dorun (repeatedly 10 exercise)) + (is (= 1 @times)))))