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

WIP: Cache restructure middleware args in HTTP endpoint macros #1400

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
2123f56
red test showing :body schema is reevaluated per request
frenchy64 Dec 12, 2023
31602ff
more red tests
frenchy64 Dec 12, 2023
1689b6c
more
frenchy64 Dec 12, 2023
8da1265
scoping test
frenchy64 Dec 12, 2023
e3bab6c
tests
frenchy64 Dec 12, 2023
e73ebe9
body
frenchy64 Dec 12, 2023
c951b12
dexpand-1 tests
frenchy64 Dec 12, 2023
bd2dce0
[skip ci]
frenchy64 Dec 12, 2023
3d66b97
fix some tests
frenchy64 Dec 12, 2023
1d15ee0
[skip ci] doc
frenchy64 Dec 12, 2023
6611f89
wip
frenchy64 Dec 12, 2023
014db66
wip
frenchy64 Dec 13, 2023
101f2ff
more
frenchy64 Dec 13, 2023
1a53270
try
frenchy64 Dec 13, 2023
bffa9d2
fix some tests
frenchy64 Dec 13, 2023
fbed803
[skip ci]
frenchy64 Dec 13, 2023
a78dcbd
add benchmark
frenchy64 Dec 13, 2023
b643872
better benchmark
frenchy64 Dec 13, 2023
cebf1d7
bench
frenchy64 Dec 13, 2023
d245327
[skip ci]
frenchy64 Dec 13, 2023
f5412ff
run bench
frenchy64 Dec 13, 2023
dac5c8a
fix bench
frenchy64 Dec 13, 2023
9ee2ebb
bench
frenchy64 Dec 13, 2023
e16443d
bench
frenchy64 Dec 13, 2023
10cb88b
don't eval swagger things
frenchy64 Dec 13, 2023
405ea34
wip
frenchy64 Dec 13, 2023
bffe1cf
capabilities test
frenchy64 Dec 13, 2023
86a6cd9
red tests for :path-params
frenchy64 Dec 13, 2023
466a406
cache :path-params
frenchy64 Dec 13, 2023
f3a52b5
wip
frenchy64 Dec 13, 2023
6d739e4
tests
frenchy64 Dec 13, 2023
e72fe34
wip
frenchy64 Dec 13, 2023
fb8e706
wip
frenchy64 Dec 13, 2023
6ac0999
wip
frenchy64 Dec 13, 2023
32adafa
wip
frenchy64 Dec 13, 2023
1d89550
test for :middleware
frenchy64 Dec 13, 2023
9c55e08
:middleware done
frenchy64 Dec 13, 2023
5bf9920
responses
frenchy64 Dec 13, 2023
e611e89
responses
frenchy64 Dec 13, 2023
8db8aa2
fix :responses
frenchy64 Dec 13, 2023
5b2c9f7
add real benchmark
frenchy64 Dec 13, 2023
9155dc2
benchmark shutdown
frenchy64 Dec 13, 2023
d502356
results
frenchy64 Dec 13, 2023
9dbf536
[skip ci] comment
frenchy64 Dec 13, 2023
ef31e05
reduce-kv
frenchy64 Dec 13, 2023
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
39 changes: 39 additions & 0 deletions benchmarks/ctia/bundle/routes_bench.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
(ns ctia.bundle.routes-bench
(:require [ctia.test-helpers
[benchmark :refer [cleanup-ctia!
setup-ctia-es-store!]]
[core :as helpers :refer [POST]]]
[ctim.examples.bundles
:refer [new-bundle-minimal]]
[perforate.core :refer :all]))

(def empty-bundle new-bundle-minimal)

(defgoal import-bundle "Import Bundle"
:setup (fn [] [(setup-ctia-es-store!)])
:cleanup (fn [{:keys [app]}] (cleanup-ctia! app)))

;; lein bench bundle
;;
;; Goal: Import Bundle
;; -----
;; Case: :empty-bundle-import-es-store
;; Evaluation count : 110460 in 60 samples of 1841 calls.
;; Execution time mean : 1.854401 ms
;; Execution time std-deviation : 2.564676 ms
;; Execution time lower quantile : 631.847954 µs ( 2.5%)
;; Execution time upper quantile : 8.998960 ms (97.5%)
;;
;; Found 7 outliers in 60 samples (11.6667 %)
;; low-severe 7 (11.6667 %)
;; Variance from outliers : 98.3202 % Variance is severely inflated by outliers


(defn play [app fixture]
(POST app
"ctia/bundle/import"
:body fixture
:headers {"Authorization" "45c1f5e3f05d0"}))

(defcase import-bundle :empty-bundle-import-es-store
[{:keys [app]}] (play app empty-bundle))
6 changes: 4 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@
"-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false"]}
:bench {:dependencies [[perforate ~perforate-version]
[criterium "0.4.5"]
[criterium "0.4.6"]
[org.clojure/test.check ~test-check-version]
[com.gfredericks/test.chuck ~test-chuck-version]
[prismatic/schema-generators ~schema-generators-version]]
Expand Down Expand Up @@ -273,7 +273,9 @@
{:name :bulk
:namespaces [ctia.bulk.routes-bench]}
{:name :migration
:namespaces [ctia.tasks.migrate-es-stores-bench]}]}
:namespaces [ctia.tasks.migrate-es-stores-bench]}
{:name :bundle
:namespaces [ctia.bundle.routes-bench]}]}
;; use `lein deps :plugins-tree` to inspect conflicts
:plugins [[lein-shell "0.5.0"]
[org.clojure/clojure ~clj-version] ;override perforate
Expand Down
171 changes: 163 additions & 8 deletions src/ctia/lib/compojure/api/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,166 @@
options)
~groutes))))

(defmacro GET {:style/indent 2} [& args] `(core/GET ~@args))
(defmacro ANY {:style/indent 2} [& args] `(core/ANY ~@args))
(defmacro HEAD {:style/indent 2} [& args] `(core/HEAD ~@args))
(defmacro PATCH {:style/indent 2} [& args] `(core/PATCH ~@args))
(defmacro DELETE {:style/indent 2} [& args] `(core/DELETE ~@args))
(defmacro OPTIONS {:style/indent 2} [& args] `(core/OPTIONS ~@args))
(defmacro POST {:style/indent 2} [& args] `(core/POST ~@args))
(defmacro PUT {:style/indent 2} [& args] `(core/PUT ~@args))
(defn ^:private restructure-endpoint
"Ensures endpoint options like :body, :return etc only initialize once
by either let-binding expressions, or throwing exceptions if reevaluation is possible
but automatic let-binding is not safe."
[compojure-macro path arg args]
(let [[options body] (common/extract-parameters args true)]
(if (= [] arg)
;; can safely let-bind values from its middleware to outside this endpoint
;; since it doesn't bind any variables (e.g., req)
(let [{:keys [lets options]} (reduce-kv (fn [acc k v]
(let [g (*gensym* (name k))
[lets v] (case k
;; (ANY "*" [] :return SCHEMA ...)
;; =>
;; (let [return__0 SCHEMA] (core/ANY "*" [] :return return__0 ...))
(:capabilities :return) [[g v] g]
;; (ANY "*" [] :body [sym SCHEMA ...] ...)
;; =>
;; (let [body__0 SCHEMA] (core/ANY "*" [] :body [sym body__0 ...] ...))
(:body :query) (do (assert (vector? v))
(assert (<= 2 (count v) 3))
[[g (nth v 1)] (assoc v 1 g)])
;; (ANY "/:left/:right" [] :path-params [left :- SCHEMA0, {right :- SCHEMA1 default}] ...)
;; =>
;; (let [left__0 SCHEMA0, right__1 SCHEMA1, right-default__2 default]
;; (core/ANY "/:left/:right" []
;; :path-params [left :- left__0, {right :- right__1 right-default__2}]
;; ...))
(:query-params :path-params)
(let [_ (assert (vector? v))]
(loop [todo v
lets []
v []]
(if (empty? todo)
[lets v]
(let [[fst] todo]
(if (symbol? fst)
;; foo :- schema
(let [[_ |- s :as all] (take 3 todo)
_ (assert (= 3 (count all)))
_ (assert (= :- |-))
g (*gensym* (name fst))]
(recur (drop 3 todo)
(conj lets g s)
(conj v fst |- g)))
;; {foo :- schema default}
(do (assert (map? fst))
(assert (= 2 (count fst)))
(let [[left right] (seq fst)
[nme-entry default-entry] (if (= :- (val left))
[left right]
[right left])
nme (key nme-entry)
_ (assert (simple-symbol? nme) nme)
g (*gensym* (name nme))
gdefault (*gensym* (str nme "-default"))]
(recur (next todo)
(conj lets
g (key default-entry)
gdefault (val default-entry))
(conj v (conj {} nme-entry [g gdefault]))))))))))

;; (ANY "*" [] :responses {401 {:schema Foo} 404 {:schema Bar}} ...)
;; =>
;; (let [responses-401__0 Foo, responses-404__1 Bar]
;; (core/ANY "*" []
;; :responses {401 {:schema responses-401__0} 404 {:schema responses-404__1}}
;; ...))
:responses (reduce-kv (fn [[lets v] code {:keys [schema] :as m}]
(assert schema)
(let [g (*gensym* (str "responses-" code))]
[(conj lets g schema) (conj v {code (assoc m :schema g)})]))
[[] {}] v)

;; (ANY "*" [] :tags #{:foo} ...)
;; =>
;; (core/ANY "*" [] :tags #{:foo} ...)
(:tags :auth-identity :identity-map :description :summary :no-doc :produces :middleware)
[[] v])]
(-> acc
(update :lets into lets)
(assoc-in [:options k] v))))
{:lets []
:options {}}
options)]
(cond->> `(~compojure-macro ~path ~arg
~@(mapcat identity options)
~@body)
(seq lets) (list `let lets)))
;; the best we can do is just assert that expressions are symbols. that will
;; force the user to let-bind them.
(do (doseq [[k v] options]
(case k
;; fail if schema is not a local/var dereference and show user how to let-bind it
(:query :body) (let [[_ s :as body] v]
(assert (vector? body))
(assert (<= 2 (count body) 3))
(when-not (symbol? s)
(throw (ex-info (str (format "Please let-bind the %s schema like so: " k)
(pr-str (list 'let ['s# s] (list (symbol (name compojure-macro)) path arg k (assoc body 1 's#) '...))))
{}))))
;; fail if right-hand-side is not a local/var dereference and show user how to let-bind it
(:return :capabilities :no-doc :produces) (when-not (or (symbol? v)
(and (= :no-doc k)
(boolean? v)))
(throw (ex-info (str (format "Please let-bind %s like so: " k)
(pr-str (list 'let ['v# v] (list (symbol (name compojure-macro)) path arg k 's# '...))))
{})))
;; fail if any schemas are not symbols
(:query-params :path-params) (let [_ (assert (vector? v))]
(loop [todo v]
(when-first [fst todo]
(if (symbol? fst)
;; foo :- schema
(let [[_ |- s :as all] (take 3 todo)]
(assert (= 3 (count all)))
(when-not (symbol? s)
(throw (ex-info (str (format "Please let-bind %s in %s like so: " fst k)
(pr-str (list 'let ['s# s] (list (symbol (name compojure-macro)) path arg k [fst |- 's#] '...))))
{})))
(recur (drop 3 todo)))
;; {foo :- schema default}
(do (assert (map? fst))
(assert (= 2 (count fst)))
(let [[left right] (seq fst)
[[nme] [schema default]] (if (= :- (val left))
[left right]
[right left])
_ (assert (simple-symbol? nme) nme)]
(when-not (and (symbol? schema)
((some-fn symbol? boolean? nil?) default))
(throw (ex-info (str (format "Please let-bind %s in %s like so: " nme k)
(pr-str (list 'let ['s# schema 'd# default]
(list (symbol (name compojure-macro)) path arg k
(array-map nme :- 's# 'd#)
'...))))
{}))))
(recur (next todo)))))))
;; swagger only
(:description :summary) nil
;; values
:tags nil
;; binders
(:auth-identity :identity-map) nil
:middleware nil
:responses (doseq [[code {:keys [schema] :as m}] v]
(when-not (symbol? schema)
(throw (ex-info (str (format "Please let-bind %s in %s like so: " code k)
(pr-str (list 'let ['s# schema]
(list (symbol (name compojure-macro)) path arg k
{code (into (sorted-map) (assoc m :schema 's#))}
'...))))
{}))()))))
(list* compojure-macro path arg args)))))

(defmacro GET {:style/indent 2} [path arg & args] (restructure-endpoint `core/GET path arg args))
(defmacro ANY {:style/indent 2} [path arg & args] (restructure-endpoint `core/ANY path arg args))
(defmacro HEAD {:style/indent 2} [path arg & args] (restructure-endpoint `core/HEAD path arg args))
(defmacro PATCH {:style/indent 2} [path arg & args] (restructure-endpoint `core/PATCH path arg args))
(defmacro DELETE {:style/indent 2} [path arg & args] (restructure-endpoint `core/DELETE path arg args))
(defmacro OPTIONS {:style/indent 2} [path arg & args] (restructure-endpoint `core/OPTIONS path arg args))
(defmacro POST {:style/indent 2} [path arg & args] (restructure-endpoint `core/POST path arg args))
(defmacro PUT {:style/indent 2} [path arg & args] (restructure-endpoint `core/PUT path arg args))
Loading
Loading