From 510be715619aa97b0e4ba53b246c27e0b1e1f89b Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 27 Aug 2024 17:51:25 -0500 Subject: [PATCH 01/39] wip --- examples/clj-kondo-hooks/.clj-kondo/.keep | 0 examples/clj-kondo-hooks/.gitignore | 5 +++++ examples/clj-kondo-hooks/deps.edn | 19 +++++++++++++++++++ examples/clj-kondo-hooks/script/clj-kondo | 7 +++++++ examples/clj-kondo-hooks/script/lint | 3 +++ examples/clj-kondo-hooks/script/prep-lint | 4 ++++ .../script/regen-expected-output | 6 ++++++ examples/clj-kondo-hooks/script/repl | 3 +++ examples/clj-kondo-hooks/script/test | 9 +++++++++ .../compojure_api_example/clj_kondo_hooks.clj | 2 ++ 10 files changed, 58 insertions(+) create mode 100644 examples/clj-kondo-hooks/.clj-kondo/.keep create mode 100644 examples/clj-kondo-hooks/.gitignore create mode 100644 examples/clj-kondo-hooks/deps.edn create mode 100755 examples/clj-kondo-hooks/script/clj-kondo create mode 100755 examples/clj-kondo-hooks/script/lint create mode 100755 examples/clj-kondo-hooks/script/prep-lint create mode 100755 examples/clj-kondo-hooks/script/regen-expected-output create mode 100755 examples/clj-kondo-hooks/script/repl create mode 100755 examples/clj-kondo-hooks/script/test create mode 100644 examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj diff --git a/examples/clj-kondo-hooks/.clj-kondo/.keep b/examples/clj-kondo-hooks/.clj-kondo/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/clj-kondo-hooks/.gitignore b/examples/clj-kondo-hooks/.gitignore new file mode 100644 index 00000000..4c8f06d3 --- /dev/null +++ b/examples/clj-kondo-hooks/.gitignore @@ -0,0 +1,5 @@ +.cpcache +.clj-kondo/.cache +.clj-kondo/imports +.clj-kondo/metosin +output/actual-output diff --git a/examples/clj-kondo-hooks/deps.edn b/examples/clj-kondo-hooks/deps.edn new file mode 100644 index 00000000..f1d06aa0 --- /dev/null +++ b/examples/clj-kondo-hooks/deps.edn @@ -0,0 +1,19 @@ +{:deps {org.clojure/clojure {:mvn/version "1.11.1"} + metosin/compojure-api {:local/root "../.."}} + :aliases {:dev {:extra-paths ["test"]} + :clj-kondo + {:replace-deps {clj-kondo/clj-kondo {:mvn/version "RELEASE"}} + :main-opts ["-m" "clj-kondo.main"]} + :test {:extra-deps {io.github.cognitect-labs/test-runner + {:git/tag "v0.5.0" :git/sha "b3fd0d2"}} + :main-opts ["-m" "cognitect.test-runner"] + :exec-fn cognitect.test-runner.api/test} + :nREPL + {:extra-deps + {cider/cider-nrepl {:mvn/version "0.28.2"}, + cider/piggieback {:mvn/version "0.5.3"}, + net.cgrand/parsley {:mvn/version "0.9.3"}, + nrepl/nrepl {:mvn/version "0.8.3"}, + reply/reply {:mvn/version "0.5.1"}}, + :jvm-opts ["-XX:-OmitStackTraceInFastThrow"] + :main-opts ["-m" "nrepl.cmdline" "--interactive"]}}} diff --git a/examples/clj-kondo-hooks/script/clj-kondo b/examples/clj-kondo-hooks/script/clj-kondo new file mode 100755 index 00000000..00ce0795 --- /dev/null +++ b/examples/clj-kondo-hooks/script/clj-kondo @@ -0,0 +1,7 @@ +#!/bin/bash + +if command -v clj-kondo &> /dev/null; then + clj-kondo "$@" +else + clojure -M:clj-kondo "$@" +fi diff --git a/examples/clj-kondo-hooks/script/lint b/examples/clj-kondo-hooks/script/lint new file mode 100755 index 00000000..d98c0a94 --- /dev/null +++ b/examples/clj-kondo-hooks/script/lint @@ -0,0 +1,3 @@ +#!/bin/bash + +./script/clj-kondo --lint src --config '{:output {:format :text :summary false}}' diff --git a/examples/clj-kondo-hooks/script/prep-lint b/examples/clj-kondo-hooks/script/prep-lint new file mode 100755 index 00000000..214e3464 --- /dev/null +++ b/examples/clj-kondo-hooks/script/prep-lint @@ -0,0 +1,4 @@ +#!/bin/bash + +rm -fr .clj-kondo/metosin +./script/clj-kondo --lint "$(clojure -Spath)" --copy-configs --skip-lint diff --git a/examples/clj-kondo-hooks/script/regen-expected-output b/examples/clj-kondo-hooks/script/regen-expected-output new file mode 100755 index 00000000..cdb94912 --- /dev/null +++ b/examples/clj-kondo-hooks/script/regen-expected-output @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +./script/prep-lint +./script/lint > output/expected-output diff --git a/examples/clj-kondo-hooks/script/repl b/examples/clj-kondo-hooks/script/repl new file mode 100755 index 00000000..b81b9f41 --- /dev/null +++ b/examples/clj-kondo-hooks/script/repl @@ -0,0 +1,3 @@ +#!/bin/bash + +clojure -M:dev:nREPL diff --git a/examples/clj-kondo-hooks/script/test b/examples/clj-kondo-hooks/script/test new file mode 100755 index 00000000..e5541ae9 --- /dev/null +++ b/examples/clj-kondo-hooks/script/test @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +./script/prep-lint +set +e +./script/lint > output/actual-output +set -e +diff output/expected-output output/actual-output diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj new file mode 100644 index 00000000..a95ec100 --- /dev/null +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -0,0 +1,2 @@ +(ns compojure-api-example.clj-kondo-hooks + (:require [])) From 9b6a969da591fb041265db47ad889531afff2852 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 27 Aug 2024 17:58:03 -0500 Subject: [PATCH 02/39] wip --- deps.edn | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 deps.edn diff --git a/deps.edn b/deps.edn new file mode 100644 index 00000000..e2592159 --- /dev/null +++ b/deps.edn @@ -0,0 +1,10 @@ +{:deps {prismatic/plumbing {:mvn/version "0.6.0"} + cheshire/cheshire {:mvn/version "5.13.0"} + compojure/compojure {:mvn/version "1.6.1"} + prismatic/schema {:mvn/version "1.1.12"} + org.tobereplaced/lettercase {:mvn/version "1.0.0"} + frankiesardo/linked {:mvn/version "1.3.0"} + ring-middleware-format/ring-middleware-format {:mvn/version "0.7.4"} + metosin/ring-http-response {:mvn/version "0.9.1"} + metosin/ring-swagger {:mvn/version "1.0.0"} + metosin/ring-swagger-ui {:mvn/version "2.2.10"}}} From 7bbde7e133f34c94f74ccd6333e860885e2e184f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 27 Aug 2024 19:31:49 -0500 Subject: [PATCH 03/39] wip --- .../compojure-api/compojure/api/meta.clj | 65 +++++++++++++++++++ .../metosin/compojure/api/core.clj | 53 +++++++++++++++ .../clj-kondo.exports/metosin/config.edn | 3 + scripts/regen-kondo.clj | 10 +++ src/compojure/api/{common.clj => common.cljc} | 6 +- 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj create mode 100644 resources/clj-kondo.exports/metosin/compojure/api/core.clj create mode 100644 resources/clj-kondo.exports/metosin/config.edn create mode 100755 scripts/regen-kondo.clj rename src/compojure/api/{common.clj => common.cljc} (96%) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj new file mode 100644 index 00000000..eea8fd59 --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -0,0 +1,65 @@ +(ns compojure.api.meta + (:require [compojure.api.common :as common :refer [extract-parameters]])) + +(defmulti restructure-param + "Restructures a key value pair in smart routes. By default the key + is consumed form the :parameters map in acc. k = given key, v = value." + (fn [k v acc] k)) + +(defn restructure [method [path arg & args] {:keys [context?]}] + (let [[options body] (extract-parameters args true) + [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) + + {:keys [lets + letks + responses + middleware + middlewares + swagger + parameters + body]} (reduce + (fn [acc [k v]] + (restructure-param k v (update-in acc [:parameters] dissoc k))) + {:lets lets + :letks [] + :responses nil + :middleware [] + :swagger {} + :body body} + options) + + ;; migration helpers + _ (assert (not middlewares) ":middlewares is deprecated with 1.0.0, use :middleware instead.") + _ (assert (not parameters) ":parameters is deprecated with 1.0.0, use :swagger instead.") + + ;; response coercion middleware, why not just code? + middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] + + (if context? + + ;; context + (let [form `(compojure.core/routes ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) + form `(compojure.core/context ~path ~arg-with-request ~form) + + ;; create and apply a separate lookup-function to find the inner routes + childs (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(compojure.core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form)] + + `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) + + ;; endpoints + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (compojure.core/compile-route method path arg-with-request (list form)) + form (if (seq middleware) `(compojure.core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] + + `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) diff --git a/resources/clj-kondo.exports/metosin/compojure/api/core.clj b/resources/clj-kondo.exports/metosin/compojure/api/core.clj new file mode 100644 index 00000000..81017922 --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure/api/core.clj @@ -0,0 +1,53 @@ +(ns compojure.api.core + (:require [compojure.api.meta :as meta] + [compojure.api.routes :as-alias routes] + [compojure.api.middleware :as-alias mw])) + +;; simulate clojure.tools.macro/name-with-attributes +(defn- name-with-attributes [name routes] + (let [routes (cond-> routes + (string? (first routes)) next) + routes (cond-> routes + (map? (first routes)) next)] + [name routes])) + +(defmacro defroutes + "Define a Ring handler function from a sequence of routes. + The name may optionally be followed by a doc-string and metadata map." + {:style/indent 1} + [name & routes] + (let [[name routes] (name-with-attributes name routes)] + `(def ~name (routes ~@routes)))) + +(defmacro let-routes + "Takes a vector of bindings and a body of routes. + + Equivalent to: `(let [...] (routes ...))`" + {:style/indent 1} + [bindings & body] + `(let ~bindings (routes ~@body))) + +(defmacro middleware + "Wraps routes with given middlewares using thread-first macro. + + Note that middlewares will be executed even if routes in body + do not match the request uri. Be careful with middleware that + has side-effects." + {:style/indent 1 + :deprecated "1.1.14" + :superseded-by "route-middleware"} + [middleware & body] + `(let [body# (routes ~@body) + wrap-mw# (mw/compose-middleware ~middleware)] + (routes/create nil nil {} [body#] (wrap-mw# body#)))) + +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env})) + +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil)) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil)) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil)) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil)) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil)) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args nil)) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil)) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil)) diff --git a/resources/clj-kondo.exports/metosin/config.edn b/resources/clj-kondo.exports/metosin/config.edn new file mode 100644 index 00000000..7f8180c4 --- /dev/null +++ b/resources/clj-kondo.exports/metosin/config.edn @@ -0,0 +1,3 @@ +{:hooks + {:macroexpand + {compojure.api.core/defroutes compojure.api.core/defroutes}}} diff --git a/scripts/regen-kondo.clj b/scripts/regen-kondo.clj new file mode 100755 index 00000000..27fa9b5c --- /dev/null +++ b/scripts/regen-kondo.clj @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +bb -f ./script/regen_kondo_config.clj + +mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure/api + +;; rename to .clj +cp src/compojure/api/common.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj diff --git a/src/compojure/api/common.clj b/src/compojure/api/common.cljc similarity index 96% rename from src/compojure/api/common.clj rename to src/compojure/api/common.cljc index 73f64f94..23951dd5 100644 --- a/src/compojure/api/common.clj +++ b/src/compojure/api/common.cljc @@ -1,5 +1,6 @@ (ns compojure.api.common - (:require [linked.core :as linked])) + #?@(:bb [] + :default [(:require [linked.core :as linked])])) (defn plain-map? "checks whether input is a map, but not a record" @@ -51,6 +52,8 @@ x y)) +#?(:bb nil + :default (defn fifo-memoize [f size] "Returns a memoized version of a referentially transparent f. The memoized version of the function keeps a cache of the mapping from arguments @@ -66,6 +69,7 @@ (dissoc mem (-> mem first first)) mem)))) value))))) +) ;; NB: when-ns eats all exceptions inside the body, including those about ;; unresolvable symbols. Keep this in mind when debugging the definitions below. From 7705d0ee4c686fb30aba950812849f8533712af6 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 27 Aug 2024 19:32:42 -0500 Subject: [PATCH 04/39] [skip ci] wip --- .../metosin/{ => compojure-api}/compojure/api/core.clj | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/clj-kondo.exports/metosin/{ => compojure-api}/compojure/api/core.clj (100%) diff --git a/resources/clj-kondo.exports/metosin/compojure/api/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj similarity index 100% rename from resources/clj-kondo.exports/metosin/compojure/api/core.clj rename to resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj From 276daf4a59cbc649d85e4c838444f48d1f54431a Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 27 Aug 2024 19:56:22 -0500 Subject: [PATCH 05/39] wip --- .../compojure-api/compojure/api/meta.clj | 257 +++++++++++++++++- src/compojure/api/{meta.clj => meta.cljc} | 6 +- 2 files changed, 247 insertions(+), 16 deletions(-) rename src/compojure/api/{meta.clj => meta.cljc} (98%) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index eea8fd59..22d3ba10 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -1,11 +1,251 @@ (ns compojure.api.meta - (:require [compojure.api.common :as common :refer [extract-parameters]])) + (:require [compojure.api.common :as common :refer [extract-parameters]] + [compojure.api.middleware :as-alias mw] + [compojure.api.routes :as-alias routes] + [plumbing.core :as-alias p] + [plumbing.fnk.impl :as-alias fnk-impl] + [ring.swagger.common :as-alias rsc] + ;[ring.swagger.json-schema :as js] + [schema.core :as-alias s] + [schema-tools.core :as-alias st] + [compojure.api.coerce :as-alias coerce] + compojure.core)) + +(def +compojure-api-request+ + "lexically bound ring-request for handlers." + '+compojure-api-request+) + +;; +;; Schema +;; + +(defn strict [schema] + (dissoc schema 'schema.core/Keyword)) + +(defn fnk-schema [bind] + (->> + (:input-schema + (fnk-impl/letk-input-schema-and-body-form + nil (with-meta bind {:schema s/Any}) [] nil)) + reverse + (into {}))) + +(s/defn src-coerce! + "Return source code for coerce! for a schema with coercion type, + extracted from a key in a ring request." + [schema, key, type :- mw/CoercionType] + (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) + `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) + +(defn- convert-return [schema] + {200 {:schema schema + ;:description (or (js/json-schema-meta schema) "") + }}) + +;; +;; Extension point +;; (defmulti restructure-param "Restructures a key value pair in smart routes. By default the key is consumed form the :parameters map in acc. k = given key, v = value." (fn [k v acc] k)) +;; +;; Pass-through swagger metadata +;; + +(defmethod restructure-param :summary [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :description [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :operationId [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :consumes [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :produces [k v acc] + (update-in acc [:swagger] assoc k v)) + +;; +;; Smart restructurings +;; + +; Boolean to discard the route out from api documentation +; Example: +; :no-doc true +(defmethod restructure-param :no-doc [_ v acc] + (update-in acc [:swagger] assoc :x-no-doc v)) + +; publishes the data as swagger-parameters without any side-effects / coercion. +; Examples: +; :swagger {:responses {200 {:schema User} +; 404 {:schema Error +; :description "Not Found"} } +; :paramerers {:query {:q s/Str} +; :body NewUser}}} +(defmethod restructure-param :swagger [_ swagger acc] + (assoc-in acc [:swagger :swagger] swagger)) + +; Route name, used with path-for +; Example: +; :name :user-route +(defmethod restructure-param :name [_ v acc] + (update-in acc [:swagger] assoc :x-name v)) + +; Tags for api categorization. Ignores duplicates. +; Examples: +; :tags [:admin] +(defmethod restructure-param :tags [_ tags acc] + (update-in acc [:swagger :tags] (comp set into) tags)) + +; Defines a return type and coerces the return value of a body against it. +; Examples: +; :return MySchema +; :return {:value String} +; :return #{{:key (s/maybe Long)}} +(defmethod restructure-param :return [_ schema acc] + (let [response (convert-return schema)] + (-> acc + (update-in [:swagger :responses] (fnil conj []) response) + (update-in [:responses] (fnil conj []) response)))) + +; value is a map of http-response-code -> Schema. Translates to both swagger +; parameters and return schema coercion. Schemas can be decorated with meta-data. +; Examples: +; :responses {403 nil} +; :responses {403 {:schema ErrorEnvelope}} +; :responses {403 {:schema ErrorEnvelope, :description \"Underflow\"}} +(defmethod restructure-param :responses [_ responses acc] + (-> acc + (update-in [:swagger :responses] (fnil conj []) responses) + (update-in [:responses] (fnil conj []) responses))) + +; reads body-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :body [user User] +(defmethod restructure-param :body [_ [value schema] acc] + (-> acc + (update-in [:lets] into [value (src-coerce! schema :body-params :body)]) + (assoc-in [:swagger :parameters :body] schema))) + +; reads query-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :query [user User] +(defmethod restructure-param :query [_ [value schema] acc] + (-> acc + (update-in [:lets] into [value (src-coerce! schema :query-params :string)]) + (assoc-in [:swagger :parameters :query] schema))) + +; reads header-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :headers [headers Headers] +(defmethod restructure-param :headers [_ [value schema] acc] + (-> acc + (update-in [:lets] into [value (src-coerce! schema :headers :string)]) + (assoc-in [:swagger :parameters :header] schema))) + +; restructures body-params with plumbing letk notation. Example: +; :body-params [id :- Long name :- String] +(defmethod restructure-param :body-params [_ body-params acc] + (let [schema (strict (fnk-schema body-params))] + (-> acc + (update-in [:letks] into [body-params (src-coerce! schema :body-params :body)]) + (assoc-in [:swagger :parameters :body] schema)))) + +; restructures form-params with plumbing letk notation. Example: +; :form-params [id :- Long name :- String] +(defmethod restructure-param :form-params [_ form-params acc] + (let [schema (strict (fnk-schema form-params))] + (-> acc + (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) + (update-in [:swagger :parameters :formData] st/merge schema) + (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) + +; restructures multipart-params with plumbing letk notation and consumes "multipart/form-data" +; :multipart-params [file :- compojure.api.upload/TempFileUpload] +(defmethod restructure-param :multipart-params [_ params acc] + (let [schema (strict (fnk-schema params))] + (-> acc + (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) + (update-in [:swagger :parameters :formData] st/merge schema) + (assoc-in [:swagger :consumes] ["multipart/form-data"])))) + +; restructures header-params with plumbing letk notation. Example: +; :header-params [id :- Long name :- String] +(defmethod restructure-param :header-params [_ header-params acc] + (let [schema (fnk-schema header-params)] + (-> acc + (update-in [:letks] into [header-params (src-coerce! schema :headers :string)]) + (assoc-in [:swagger :parameters :header] schema)))) + +; restructures query-params with plumbing letk notation. Example: +; :query-params [id :- Long name :- String] +(defmethod restructure-param :query-params [_ query-params acc] + (let [schema (fnk-schema query-params)] + (-> acc + (update-in [:letks] into [query-params (src-coerce! schema :query-params :string)]) + (assoc-in [:swagger :parameters :query] schema)))) + +; restructures path-params by plumbing letk notation. Example: +; :path-params [id :- Long name :- String] +(defmethod restructure-param :path-params [_ path-params acc] + (let [schema (fnk-schema path-params)] + (-> acc + (update-in [:letks] into [path-params (src-coerce! schema :route-params :string)]) + (assoc-in [:swagger :parameters :path] schema)))) + +; Applies the given vector of middlewares to the route +(defmethod restructure-param :middleware [_ middleware acc] + (update-in acc [:middleware] into middleware)) + +; Bind to stuff in request components using letk syntax +(defmethod restructure-param :components [_ components acc] + (update-in acc [:letks] into [components `(mw/get-components ~+compojure-api-request+)])) + +; route-specific override for coercers +(defmethod restructure-param :coercion [_ coercion acc] + (update-in acc [:middleware] conj [mw/wrap-coercion coercion])) + +;; +;; Api +;; + +(defn- destructure-compojure-api-request + "Returns a vector of four elements: + - pruned path string + - new lets list + - bindings form for compojure route + - symbol to which request will be bound" + [path arg] + (let [path-string (if (vector? path) (first path) path)] + (cond + ;; GET "/route" [] + (vector? arg) [path-string [] (into arg [:as +compojure-api-request+]) +compojure-api-request+] + ;; GET "/route" {:as req} + (map? arg) (if-let [as (:as arg)] + [path-string [+compojure-api-request+ as] arg as] + [path-string [] (merge arg [:as +compojure-api-request+]) +compojure-api-request+]) + ;; GET "/route" req + (symbol? arg) [path-string [+compojure-api-request+ arg] arg arg] + :else (throw + (RuntimeException. + (str "unknown compojure destruction syntax: " arg)))))) + +(defn merge-parameters + "Merge parameters at runtime to allow usage of runtime-paramers with route-macros." + [{:keys [responses swagger] :as parameters}] + (cond-> parameters + ;(seq responses) (assoc :responses (common/merge-vector responses)) + ;swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)) + )) + (defn restructure [method [path arg & args] {:keys [context?]}] (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) @@ -42,18 +282,9 @@ form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) - form `(compojure.core/context ~path ~arg-with-request ~form) - - ;; create and apply a separate lookup-function to find the inner routes - childs (let [form (vec body) - form (if (seq letks) `(dummy-letk ~letks ~form) form) - form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(compojure.core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) - form `(fn [~'+compojure-api-request+] ~form) - form `(~form {})] - form)] - - `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) + form `(compojure.core/context ~path ~arg-with-request ~form)] + + `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form)) ;; endpoints (let [form `(do ~@body) diff --git a/src/compojure/api/meta.clj b/src/compojure/api/meta.cljc similarity index 98% rename from src/compojure/api/meta.clj rename to src/compojure/api/meta.cljc index 1b093871..0286adc3 100644 --- a/src/compojure/api/meta.clj +++ b/src/compojure/api/meta.cljc @@ -217,13 +217,13 @@ ;; (defmacro dummy-let - "Dummy let-macro used in resolving route-docs. not part of normal invokation chain." + "Dummy let-macro used in resolving route-docs. not part of normal invocation chain." [bindings & body] (let [bind-form (vec (apply concat (for [n (take-nth 2 bindings)] [n nil])))] `(let ~bind-form ~@body))) (defmacro dummy-letk - "Dummy letk-macro used in resolving route-docs. not part of normal invokation chain." + "Dummy letk-macro used in resolving route-docs. not part of normal invocation chain." [bindings & body] (reduce (fn [cur-body-form [bind-form]] @@ -270,7 +270,7 @@ (str "unknown compojure destruction syntax: " arg)))))) (defn merge-parameters - "Merge parameters at runtime to allow usage of runtime-paramers with route-macros." + "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." [{:keys [responses swagger] :as parameters}] (cond-> parameters (seq responses) (assoc :responses (common/merge-vector responses)) From 852c54dc2924da1cb5e54e2e365b4c130d1244c4 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 27 Aug 2024 20:04:26 -0500 Subject: [PATCH 06/39] [skip ci] --- .../src/compojure_api_example/clj_kondo_hooks.clj | 7 ++++++- resources/clj-kondo.exports/metosin/config.edn | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index a95ec100..51da7742 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -1,2 +1,7 @@ (ns compojure-api-example.clj-kondo-hooks - (:require [])) + (:require [compojure.api.sweet :as sweet] + [ring.util.http-response :as resp])) + +(sweet/GET "/30" [] (resp/ok {:result 30})) +(sweet/GET "/30" req + (resp/ok {:result (:body req)})) diff --git a/resources/clj-kondo.exports/metosin/config.edn b/resources/clj-kondo.exports/metosin/config.edn index 7f8180c4..8a5a09f5 100644 --- a/resources/clj-kondo.exports/metosin/config.edn +++ b/resources/clj-kondo.exports/metosin/config.edn @@ -1,3 +1,4 @@ {:hooks {:macroexpand - {compojure.api.core/defroutes compojure.api.core/defroutes}}} + {compojure.api.sweet/GET compojure.api.core/GET + compojure.api.core/defroutes compojure.api.core/defroutes}}} From 0ad857ff57a81f670b6a233cfe3c4d9f0f502c6b Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Thu, 29 Aug 2024 15:30:46 -0500 Subject: [PATCH 07/39] refix --- src/compojure/api/meta.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 0286adc3..d1041be6 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -84,7 +84,7 @@ ; :swagger {:responses {200 {:schema User} ; 404 {:schema Error ; :description "Not Found"} } -; :paramerers {:query {:q s/Str} +; :parameters {:query {:q s/Str} ; :body NewUser}}} (defmethod restructure-param :swagger [_ swagger acc] (assoc-in acc [:swagger :swagger] swagger)) From da3ffb1a51e644ddb31c01217fcbaa68a059a0ae Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 12:10:54 -0500 Subject: [PATCH 08/39] revert --- src/compojure/api/meta.cljc | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index d1041be6..8bac5b35 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -127,7 +127,11 @@ ; second is the Schema to be coerced! against. ; Examples: ; :body [user User] -(defmethod restructure-param :body [_ [value schema] acc] +(defmethod restructure-param :body [_ [value schema :as bv] acc] + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) + (assert (= 2 (count bv)) + (str ":body should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true"))) (-> acc (update-in [:lets] into [value (src-coerce! schema :body-params :body)]) (assoc-in [:swagger :parameters :body] schema))) @@ -136,7 +140,11 @@ ; second is the Schema to be coerced! against. ; Examples: ; :query [user User] -(defmethod restructure-param :query [_ [value schema] acc] +(defmethod restructure-param :query [_ [value schema :as bv] acc] + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) + (assert (= 2 (count bv)) + (str ":query should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true"))) (-> acc (update-in [:lets] into [value (src-coerce! schema :query-params :string)]) (assoc-in [:swagger :parameters :query] schema))) @@ -145,7 +153,12 @@ ; second is the Schema to be coerced! against. ; Examples: ; :headers [headers Headers] -(defmethod restructure-param :headers [_ [value schema] acc] +(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"))) + (-> acc (update-in [:lets] into [value (src-coerce! schema :headers :string)]) (assoc-in [:swagger :parameters :header] schema))) From 2e33f455c8933500e13a434fc966e405edc36abc Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 12:47:07 -0500 Subject: [PATCH 09/39] inline tools.macro --- README.md | 11 ++ epl-v10.html | 261 ++++++++++++++++++++++++++++++++++++ src/compojure/api/core.clj | 5 +- src/compojure/api/meta.cljc | 30 +++++ 4 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 epl-v10.html diff --git a/README.md b/README.md index 914f95b3..e1b70816 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,17 @@ lein new compojure-api my-api +clojure-test ## License +Copied code from tools.macro has license: + +``` +Copyright (c) Rich Hickey. All rights reserved. +The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (https://opensource.org/license/epl-1-0/) +which can be found in the file epl-v10.html at the root of this distribution. By using this software in any fashion, you are agreeing to +be bound bythe terms of this license. You must not remove this notice, or any other, from this software. +``` + +All other code: + Copyright © 2014-2016 [Metosin Oy](https://www.metosin.fi) Distributed under the Eclipse Public License, the same as Clojure. diff --git a/epl-v10.html b/epl-v10.html new file mode 100644 index 00000000..813c07d8 --- /dev/null +++ b/epl-v10.html @@ -0,0 +1,261 @@ + + + + + + +Eclipse Public License - Version 1.0 + + + + + + +

Eclipse Public License - v 1.0

+ +

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE +PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR +DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS +AGREEMENT.

+ +

1. DEFINITIONS

+ +

"Contribution" means:

+ +

a) in the case of the initial Contributor, the initial +code and documentation distributed under this Agreement, and

+

b) in the case of each subsequent Contributor:

+

i) changes to the Program, and

+

ii) additions to the Program;

+

where such changes and/or additions to the Program +originate from and are distributed by that particular Contributor. A +Contribution 'originates' from a Contributor if it was added to the +Program by such Contributor itself or anyone acting on such +Contributor's behalf. Contributions do not include additions to the +Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) +are not derivative works of the Program.

+ +

"Contributor" means any person or entity that distributes +the Program.

+ +

"Licensed Patents" mean patent claims licensable by a +Contributor which are necessarily infringed by the use or sale of its +Contribution alone or when combined with the Program.

+ +

"Program" means the Contributions distributed in accordance +with this Agreement.

+ +

"Recipient" means anyone who receives the Program under +this Agreement, including all Contributors.

+ +

2. GRANT OF RIGHTS

+ +

a) Subject to the terms of this Agreement, each +Contributor hereby grants Recipient a non-exclusive, worldwide, +royalty-free copyright license to reproduce, prepare derivative works +of, publicly display, publicly perform, distribute and sublicense the +Contribution of such Contributor, if any, and such derivative works, in +source code and object code form.

+ +

b) Subject to the terms of this Agreement, each +Contributor hereby grants Recipient a non-exclusive, worldwide, +royalty-free patent license under Licensed Patents to make, use, sell, +offer to sell, import and otherwise transfer the Contribution of such +Contributor, if any, in source code and object code form. This patent +license shall apply to the combination of the Contribution and the +Program if, at the time the Contribution is added by the Contributor, +such addition of the Contribution causes such combination to be covered +by the Licensed Patents. The patent license shall not apply to any other +combinations which include the Contribution. No hardware per se is +licensed hereunder.

+ +

c) Recipient understands that although each Contributor +grants the licenses to its Contributions set forth herein, no assurances +are provided by any Contributor that the Program does not infringe the +patent or other intellectual property rights of any other entity. Each +Contributor disclaims any liability to Recipient for claims brought by +any other entity based on infringement of intellectual property rights +or otherwise. As a condition to exercising the rights and licenses +granted hereunder, each Recipient hereby assumes sole responsibility to +secure any other intellectual property rights needed, if any. For +example, if a third party patent license is required to allow Recipient +to distribute the Program, it is Recipient's responsibility to acquire +that license before distributing the Program.

+ +

d) Each Contributor represents that to its knowledge it +has sufficient copyright rights in its Contribution, if any, to grant +the copyright license set forth in this Agreement.

+ +

3. REQUIREMENTS

+ +

A Contributor may choose to distribute the Program in object code +form under its own license agreement, provided that:

+ +

a) it complies with the terms and conditions of this +Agreement; and

+ +

b) its license agreement:

+ +

i) effectively disclaims on behalf of all Contributors +all warranties and conditions, express and implied, including warranties +or conditions of title and non-infringement, and implied warranties or +conditions of merchantability and fitness for a particular purpose;

+ +

ii) effectively excludes on behalf of all Contributors +all liability for damages, including direct, indirect, special, +incidental and consequential damages, such as lost profits;

+ +

iii) states that any provisions which differ from this +Agreement are offered by that Contributor alone and not by any other +party; and

+ +

iv) states that source code for the Program is available +from such Contributor, and informs licensees how to obtain it in a +reasonable manner on or through a medium customarily used for software +exchange.

+ +

When the Program is made available in source code form:

+ +

a) it must be made available under this Agreement; and

+ +

b) a copy of this Agreement must be included with each +copy of the Program.

+ +

Contributors may not remove or alter any copyright notices contained +within the Program.

+ +

Each Contributor must identify itself as the originator of its +Contribution, if any, in a manner that reasonably allows subsequent +Recipients to identify the originator of the Contribution.

+ +

4. COMMERCIAL DISTRIBUTION

+ +

Commercial distributors of software may accept certain +responsibilities with respect to end users, business partners and the +like. While this license is intended to facilitate the commercial use of +the Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create +potential liability for other Contributors. Therefore, if a Contributor +includes the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and +indemnify every other Contributor ("Indemnified Contributor") +against any losses, damages and costs (collectively "Losses") +arising from claims, lawsuits and other legal actions brought by a third +party against the Indemnified Contributor to the extent caused by the +acts or omissions of such Commercial Contributor in connection with its +distribution of the Program in a commercial product offering. The +obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In +order to qualify, an Indemnified Contributor must: a) promptly notify +the Commercial Contributor in writing of such claim, and b) allow the +Commercial Contributor to control, and cooperate with the Commercial +Contributor in, the defense and any related settlement negotiations. The +Indemnified Contributor may participate in any such claim at its own +expense.

+ +

For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those +performance claims and warranties, and if a court requires any other +Contributor to pay any damages as a result, the Commercial Contributor +must pay those damages.

+ +

5. NO WARRANTY

+ +

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS +PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, +ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to +the risks and costs of program errors, compliance with applicable laws, +damage to or loss of data, programs or equipment, and unavailability or +interruption of operations.

+ +

6. DISCLAIMER OF LIABILITY

+ +

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT +NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

+ +

7. GENERAL

+ +

If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further action +by the parties hereto, such provision shall be reformed to the minimum +extent necessary to make such provision valid and enforceable.

+ +

If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other +software or hardware) infringes such Recipient's patent(s), then such +Recipient's rights granted under Section 2(b) shall terminate as of the +date such litigation is filed.

+ +

All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of time +after becoming aware of such noncompliance. If all Recipient's rights +under this Agreement terminate, Recipient agrees to cease use and +distribution of the Program as soon as reasonably practicable. However, +Recipient's obligations under this Agreement and any licenses granted by +Recipient relating to the Program shall continue and survive.

+ +

Everyone is permitted to copy and distribute copies of this +Agreement, but in order to avoid inconsistency the Agreement is +copyrighted and may only be modified in the following manner. The +Agreement Steward reserves the right to publish new versions (including +revisions) of this Agreement from time to time. No one other than the +Agreement Steward has the right to modify this Agreement. The Eclipse +Foundation is the initial Agreement Steward. The Eclipse Foundation may +assign the responsibility to serve as the Agreement Steward to a +suitable separate entity. Each new version of the Agreement will be +given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version +of the Agreement is published, Contributor may elect to distribute the +Program (including its Contributions) under the new version. Except as +expressly stated in Sections 2(a) and 2(b) above, Recipient receives no +rights or licenses to the intellectual property of any Contributor under +this Agreement, whether expressly, by implication, estoppel or +otherwise. All rights in the Program not expressly granted under this +Agreement are reserved.

+ +

This Agreement is governed by the laws of the State of New York and +the intellectual property laws of the United States of America. No party +to this Agreement will bring a legal action under this Agreement more +than one year after the cause of action arose. Each party waives its +rights to a jury trial in any resulting litigation.

+ + + + diff --git a/src/compojure/api/core.clj b/src/compojure/api/core.clj index 07b22a14..d7a8c936 100644 --- a/src/compojure/api/core.clj +++ b/src/compojure/api/core.clj @@ -3,8 +3,7 @@ [compojure.api.async] [compojure.api.routes :as routes] [compojure.api.middleware :as mw] - [compojure.core :as compojure] - [clojure.tools.macro :as macro])) + [compojure.core :as compojure])) (defn ring-handler "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" @@ -26,7 +25,7 @@ The name may optionally be followed by a doc-string and metadata map." {:style/indent 1} [name & routes] - (let [[name routes] (macro/name-with-attributes name routes)] + (let [[name routes] (meta/name-with-attributes name routes)] `(def ~name (routes ~@routes)))) (defmacro let-routes diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 8bac5b35..5eb9f1aa 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -15,6 +15,36 @@ "lexically bound ring-request for handlers." '+compojure-api-request+) +;; https://github.com/clojure/tools.macro/blob/415512648bb51153f380823c41323cda2c13f47f/src/main/clojure/clojure/tools/macro.clj +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (https://opensource.org/license/epl-1-0/) +;; which can be found in the file epl-v10.html at the root of this distribution. By using this software in any fashion, you are agreeing to +;; be bound bythe terms of this license. You must not remove this notice, or any other, from this software. +(defn name-with-attributes + "To be used in macro definitions. + Handles optional docstrings and attribute maps for a name to be defined + in a list of macro arguments. If the first macro argument is a string, + it is added as a docstring to name and removed from the macro argument + list. If afterwards the first macro argument is a map, its entries are + added to the name's metadata map and the map is removed from the + macro argument list. The return value is a vector containing the name + with its extended metadata map and the list of unprocessed macro + arguments." + [name macro-args] + (let [[docstring macro-args] (if (string? (first macro-args)) + [(first macro-args) (next macro-args)] + [nil macro-args]) + [attr macro-args] (if (map? (first macro-args)) + [(first macro-args) (next macro-args)] + [{} macro-args]) + attr (if docstring + (assoc attr :doc docstring) + attr) + attr (if (meta name) + (conj (meta name) attr) + attr)] + [(with-meta name attr) macro-args])) + ;; ;; Schema ;; From a16a872a6161fafded3c6af2b4a42254f33bce8c Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 13:43:18 -0500 Subject: [PATCH 10/39] wip --- .../compojure-api/compojure/api/core.clj | 10 +--- src/compojure/api/{core.clj => core.cljc} | 56 ++++++++++--------- src/compojure/api/meta.cljc | 7 ++- 3 files changed, 37 insertions(+), 36 deletions(-) rename src/compojure/api/{core.clj => core.cljc} (61%) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj index 81017922..bdded967 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj @@ -3,20 +3,12 @@ [compojure.api.routes :as-alias routes] [compojure.api.middleware :as-alias mw])) -;; simulate clojure.tools.macro/name-with-attributes -(defn- name-with-attributes [name routes] - (let [routes (cond-> routes - (string? (first routes)) next) - routes (cond-> routes - (map? (first routes)) next)] - [name routes])) - (defmacro defroutes "Define a Ring handler function from a sequence of routes. The name may optionally be followed by a doc-string and metadata map." {:style/indent 1} [name & routes] - (let [[name routes] (name-with-attributes name routes)] + (let [[name routes] (meta/name-with-attributes name routes)] `(def ~name (routes ~@routes)))) (defmacro let-routes diff --git a/src/compojure/api/core.clj b/src/compojure/api/core.cljc similarity index 61% rename from src/compojure/api/core.clj rename to src/compojure/api/core.cljc index d7a8c936..292816cb 100644 --- a/src/compojure/api/core.clj +++ b/src/compojure/api/core.cljc @@ -1,9 +1,11 @@ +;; :bb reader feature assumes clj-kondo (ns compojure.api.core (:require [compojure.api.meta :as meta] - [compojure.api.async] - [compojure.api.routes :as routes] - [compojure.api.middleware :as mw] - [compojure.core :as compojure])) + #?@(:bb [] + :default [[compojure.api.async] + [compojure.core :as compojure]]) + [compojure.api.routes ?@(:bb :as-alias :default :as) routes] + [compojure.api.middleware ?@(:bb :as-alias :default :as) mw])) (defn ring-handler "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" @@ -15,10 +17,11 @@ (defn routes "Create a Ring handler by combining several handlers into one." [& handlers] - (let [handlers (seq (keep identity (flatten handlers)))] - (routes/map->Route - {:childs (vec handlers) - :handler (meta/routing handlers)}))) + #?(:bb (throw (ex-info "Not supported in bb")) + :default (let [handlers (seq (keep identity (flatten handlers)))] + (routes/map->Route + {:childs (vec handlers) + :handler (meta/routing handlers)})))) (defmacro defroutes "Define a Ring handler function from a sequence of routes. @@ -40,8 +43,9 @@ "Routes without route-documentation. Can be used to wrap routes, not satisfying compojure.api.routes/Routing -protocol." [& handlers] - (let [handlers (keep identity handlers)] - (routes/map->Route {:handler (meta/routing handlers)}))) + #?(:bb (throw (ex-info "Not supported in bb")) + :default (let [handlers (keep identity handlers)] + (routes/map->Route {:handler (meta/routing handlers)})))) (defmacro middleware "Wraps routes with given middlewares using thread-first macro. @@ -66,20 +70,22 @@ {:style/indent 1 :supercedes "middleware"} [middleware & body] - (let [handler (apply routes body) - x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] - ;; use original handler for docs and wrapped handler for implementation - (routes/map->Route - {:childs [handler] - :handler x-handler}))) + #?(:bb (throw (ex-info "Not supported in bb")) + :default + (let [handler (apply routes body) + x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] + ;; use original handler for docs and wrapped handler for implementation + (routes/map->Route + {:childs [handler] + :handler x-handler})))) -(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env})) +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:bb true :default false)})) -(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil)) -(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil)) -(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil)) -(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil)) -(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil)) -(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args nil)) -(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil)) -(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil)) +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:bb {:kondo-rule? true} :default nil))) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 5eb9f1aa..fee947cf 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -319,7 +319,9 @@ (seq responses) (assoc :responses (common/merge-vector responses)) swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) -(defn restructure [method [path arg & args] {:keys [context?]}] +(defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] + #?(:bb (assert kondo-rule?) + :default nil) (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) @@ -338,7 +340,8 @@ :responses nil :middleware [] :swagger {} - :body body} + :body body + :kondo-rule? kondo-rule?} options) ;; migration helpers From 9f101a4a50925df40d6875344f9bb4b3b8af4c9f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 13:49:49 -0500 Subject: [PATCH 11/39] wip --- src/compojure/api/core.cljc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compojure/api/core.cljc b/src/compojure/api/core.cljc index 292816cb..74db7e9e 100644 --- a/src/compojure/api/core.cljc +++ b/src/compojure/api/core.cljc @@ -4,8 +4,8 @@ #?@(:bb [] :default [[compojure.api.async] [compojure.core :as compojure]]) - [compojure.api.routes ?@(:bb :as-alias :default :as) routes] - [compojure.api.middleware ?@(:bb :as-alias :default :as) mw])) + [compojure.api.routes #?(:bb :as-alias :default :as) routes] + [compojure.api.middleware #?(:bb :as-alias :default :as) mw])) (defn ring-handler "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" From c57bd88fd6d763b5b198ad36839bf7d57856731f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 13:55:05 -0500 Subject: [PATCH 12/39] wip --- .../src/compojure_api_example/clj_kondo_hooks.clj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index 51da7742..4f8366cd 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -1,7 +1,9 @@ (ns compojure-api-example.clj-kondo-hooks - (:require [compojure.api.sweet :as sweet] + (:require ;[compojure.api.sweet :as sweet] + [compojure.api.sweet :as core] [ring.util.http-response :as resp])) -(sweet/GET "/30" [] (resp/ok {:result 30})) -(sweet/GET "/30" req - (resp/ok {:result (:body req)})) +(core/GET "/30" [] (resp/ok {:result 30})) + +(core/GET "/30" req + (resp/ok {:result (:body req)})) From 44ae57b816fe304e3ada609a9d6e5d76ec2cdc72 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 14:04:45 -0500 Subject: [PATCH 13/39] wip --- deps.edn | 3 +- .../compojure-api/compojure/api/core.clj | 45 +++ .../compojure-api/compojure/api/meta.clj | 309 ++++++++++++++++++ .../.clj-kondo/imports/metosin/config.edn | 4 + examples/clj-kondo-hooks/.gitignore | 2 - .../compojure_api_example/clj_kondo_hooks.clj | 2 +- 6 files changed, 361 insertions(+), 4 deletions(-) create mode 100644 examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj create mode 100644 examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj create mode 100644 examples/clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn diff --git a/deps.edn b/deps.edn index e2592159..86955aa2 100644 --- a/deps.edn +++ b/deps.edn @@ -1,4 +1,5 @@ -{:deps {prismatic/plumbing {:mvn/version "0.6.0"} +{:paths ["src" "resources"] + :deps {prismatic/plumbing {:mvn/version "0.6.0"} cheshire/cheshire {:mvn/version "5.13.0"} compojure/compojure {:mvn/version "1.6.1"} prismatic/schema {:mvn/version "1.1.12"} diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj new file mode 100644 index 00000000..bdded967 --- /dev/null +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj @@ -0,0 +1,45 @@ +(ns compojure.api.core + (:require [compojure.api.meta :as meta] + [compojure.api.routes :as-alias routes] + [compojure.api.middleware :as-alias mw])) + +(defmacro defroutes + "Define a Ring handler function from a sequence of routes. + The name may optionally be followed by a doc-string and metadata map." + {:style/indent 1} + [name & routes] + (let [[name routes] (meta/name-with-attributes name routes)] + `(def ~name (routes ~@routes)))) + +(defmacro let-routes + "Takes a vector of bindings and a body of routes. + + Equivalent to: `(let [...] (routes ...))`" + {:style/indent 1} + [bindings & body] + `(let ~bindings (routes ~@body))) + +(defmacro middleware + "Wraps routes with given middlewares using thread-first macro. + + Note that middlewares will be executed even if routes in body + do not match the request uri. Be careful with middleware that + has side-effects." + {:style/indent 1 + :deprecated "1.1.14" + :superseded-by "route-middleware"} + [middleware & body] + `(let [body# (routes ~@body) + wrap-mw# (mw/compose-middleware ~middleware)] + (routes/create nil nil {} [body#] (wrap-mw# body#)))) + +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env})) + +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil)) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil)) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil)) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil)) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil)) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args nil)) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil)) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil)) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj new file mode 100644 index 00000000..6a96a976 --- /dev/null +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -0,0 +1,309 @@ +(ns compojure.api.meta + (:require [compojure.api.common :as common :refer [extract-parameters]] + [compojure.api.middleware :as-alias mw] + [compojure.api.routes :as-alias routes] + [plumbing.core :as-alias p] + [plumbing.fnk.impl :as-alias fnk-impl] + [ring.swagger.common :as-alias rsc] + ;[ring.swagger.json-schema :as js] + [schema.core :as-alias s] + [schema-tools.core :as-alias st] + [compojure.api.coerce :as-alias coerce] + compojure.core)) + +(def +compojure-api-request+ + "lexically bound ring-request for handlers." + '+compojure-api-request+) + +;; +;; Schema +;; + +(defn strict [schema] + (dissoc schema 'schema.core/Keyword)) + +(defn fnk-schema [bind] + (->> + (:input-schema + (fnk-impl/letk-input-schema-and-body-form + nil (with-meta bind {:schema s/Any}) [] nil)) + reverse + (into {}))) + +(s/defn src-coerce! + "Return source code for coerce! for a schema with coercion type, + extracted from a key in a ring request." + [schema, key, type :- mw/CoercionType] + (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) + `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) + +(defn- convert-return [schema] + {200 {:schema schema + ;:description (or (js/json-schema-meta schema) "") + }}) + +;; +;; Extension point +;; + +(defmulti restructure-param + "Restructures a key value pair in smart routes. By default the key + is consumed form the :parameters map in acc. k = given key, v = value." + (fn [k v acc] k)) + +;; +;; Pass-through swagger metadata +;; + +(defmethod restructure-param :summary [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :description [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :operationId [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :consumes [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :produces [k v acc] + (update-in acc [:swagger] assoc k v)) + +;; +;; Smart restructurings +;; + +; Boolean to discard the route out from api documentation +; Example: +; :no-doc true +(defmethod restructure-param :no-doc [_ v acc] + (update-in acc [:swagger] assoc :x-no-doc v)) + +; publishes the data as swagger-parameters without any side-effects / coercion. +; Examples: +; :swagger {:responses {200 {:schema User} +; 404 {:schema Error +; :description "Not Found"} } +; :parameters {:query {:q s/Str} +; :body NewUser}}} +(defmethod restructure-param :swagger [_ swagger acc] + (assoc-in acc [:swagger :swagger] swagger)) + +; Route name, used with path-for +; Example: +; :name :user-route +(defmethod restructure-param :name [_ v acc] + (update-in acc [:swagger] assoc :x-name v)) + +; Tags for api categorization. Ignores duplicates. +; Examples: +; :tags [:admin] +(defmethod restructure-param :tags [_ tags acc] + (update-in acc [:swagger :tags] (comp set into) tags)) + +; Defines a return type and coerces the return value of a body against it. +; Examples: +; :return MySchema +; :return {:value String} +; :return #{{:key (s/maybe Long)}} +(defmethod restructure-param :return [_ schema acc] + (let [response (convert-return schema)] + (-> acc + (update-in [:swagger :responses] (fnil conj []) response) + (update-in [:responses] (fnil conj []) response)))) + +; value is a map of http-response-code -> Schema. Translates to both swagger +; parameters and return schema coercion. Schemas can be decorated with meta-data. +; Examples: +; :responses {403 nil} +; :responses {403 {:schema ErrorEnvelope}} +; :responses {403 {:schema ErrorEnvelope, :description \"Underflow\"}} +(defmethod restructure-param :responses [_ responses acc] + (-> acc + (update-in [:swagger :responses] (fnil conj []) responses) + (update-in [:responses] (fnil conj []) responses))) + +; reads body-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :body [user User] +(defmethod restructure-param :body [_ [value schema :as bv] acc] + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) + (assert (= 2 (count bv)) + (str ":body should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true"))) + (-> acc + (update-in [:lets] into [value (src-coerce! schema :body-params :body)]) + (assoc-in [:swagger :parameters :body] schema))) + +; reads query-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :query [user User] +(defmethod restructure-param :query [_ [value schema :as bv] acc] + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) + (assert (= 2 (count bv)) + (str ":query should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true"))) + (-> acc + (update-in [:lets] into [value (src-coerce! schema :query-params :string)]) + (assoc-in [:swagger :parameters :query] schema))) + +; reads header-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :headers [headers Headers] +(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"))) + + (-> acc + (update-in [:lets] into [value (src-coerce! schema :headers :string)]) + (assoc-in [:swagger :parameters :header] schema))) + +; restructures body-params with plumbing letk notation. Example: +; :body-params [id :- Long name :- String] +(defmethod restructure-param :body-params [_ body-params acc] + (let [schema (strict (fnk-schema body-params))] + (-> acc + (update-in [:letks] into [body-params (src-coerce! schema :body-params :body)]) + (assoc-in [:swagger :parameters :body] schema)))) + +; restructures form-params with plumbing letk notation. Example: +; :form-params [id :- Long name :- String] +(defmethod restructure-param :form-params [_ form-params acc] + (let [schema (strict (fnk-schema form-params))] + (-> acc + (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) + (update-in [:swagger :parameters :formData] st/merge schema) + (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) + +; restructures multipart-params with plumbing letk notation and consumes "multipart/form-data" +; :multipart-params [file :- compojure.api.upload/TempFileUpload] +(defmethod restructure-param :multipart-params [_ params acc] + (let [schema (strict (fnk-schema params))] + (-> acc + (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) + (update-in [:swagger :parameters :formData] st/merge schema) + (assoc-in [:swagger :consumes] ["multipart/form-data"])))) + +; restructures header-params with plumbing letk notation. Example: +; :header-params [id :- Long name :- String] +(defmethod restructure-param :header-params [_ header-params acc] + (let [schema (fnk-schema header-params)] + (-> acc + (update-in [:letks] into [header-params (src-coerce! schema :headers :string)]) + (assoc-in [:swagger :parameters :header] schema)))) + +; restructures query-params with plumbing letk notation. Example: +; :query-params [id :- Long name :- String] +(defmethod restructure-param :query-params [_ query-params acc] + (let [schema (fnk-schema query-params)] + (-> acc + (update-in [:letks] into [query-params (src-coerce! schema :query-params :string)]) + (assoc-in [:swagger :parameters :query] schema)))) + +; restructures path-params by plumbing letk notation. Example: +; :path-params [id :- Long name :- String] +(defmethod restructure-param :path-params [_ path-params acc] + (let [schema (fnk-schema path-params)] + (-> acc + (update-in [:letks] into [path-params (src-coerce! schema :route-params :string)]) + (assoc-in [:swagger :parameters :path] schema)))) + +; Applies the given vector of middlewares to the route +(defmethod restructure-param :middleware [_ middleware acc] + (update-in acc [:middleware] into middleware)) + +; Bind to stuff in request components using letk syntax +(defmethod restructure-param :components [_ components acc] + (update-in acc [:letks] into [components `(mw/get-components ~+compojure-api-request+)])) + +; route-specific override for coercers +(defmethod restructure-param :coercion [_ coercion acc] + (update-in acc [:middleware] conj [mw/wrap-coercion coercion])) + +;; +;; Api +;; + +(defn- destructure-compojure-api-request + "Returns a vector of four elements: + - pruned path string + - new lets list + - bindings form for compojure route + - symbol to which request will be bound" + [path arg] + (let [path-string (if (vector? path) (first path) path)] + (cond + ;; GET "/route" [] + (vector? arg) [path-string [] (into arg [:as +compojure-api-request+]) +compojure-api-request+] + ;; GET "/route" {:as req} + (map? arg) (if-let [as (:as arg)] + [path-string [+compojure-api-request+ as] arg as] + [path-string [] (merge arg [:as +compojure-api-request+]) +compojure-api-request+]) + ;; GET "/route" req + (symbol? arg) [path-string [+compojure-api-request+ arg] arg arg] + :else (throw + (RuntimeException. + (str "unknown compojure destruction syntax: " arg)))))) + +(defn merge-parameters + "Merge parameters at runtime to allow usage of runtime-paramers with route-macros." + [{:keys [responses swagger] :as parameters}] + (cond-> parameters + ;(seq responses) (assoc :responses (common/merge-vector responses)) + ;swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)) + )) + +(defn restructure [method [path arg & args] {:keys [context?]}] + (let [[options body] (extract-parameters args true) + [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) + + {:keys [lets + letks + responses + middleware + middlewares + swagger + parameters + body]} (reduce + (fn [acc [k v]] + (restructure-param k v (update-in acc [:parameters] dissoc k))) + {:lets lets + :letks [] + :responses nil + :middleware [] + :swagger {} + :body body} + options) + + ;; migration helpers + _ (assert (not middlewares) ":middlewares is deprecated with 1.0.0, use :middleware instead.") + _ (assert (not parameters) ":parameters is deprecated with 1.0.0, use :swagger instead.") + + ;; response coercion middleware, why not just code? + middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] + + (if context? + + ;; context + (let [form `(compojure.core/routes ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) + form `(compojure.core/context ~path ~arg-with-request ~form)] + + `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form)) + + ;; endpoints + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (compojure.core/compile-route method path arg-with-request (list form)) + form (if (seq middleware) `(compojure.core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] + + `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn new file mode 100644 index 00000000..8a5a09f5 --- /dev/null +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn @@ -0,0 +1,4 @@ +{:hooks + {:macroexpand + {compojure.api.sweet/GET compojure.api.core/GET + compojure.api.core/defroutes compojure.api.core/defroutes}}} diff --git a/examples/clj-kondo-hooks/.gitignore b/examples/clj-kondo-hooks/.gitignore index 4c8f06d3..179043b0 100644 --- a/examples/clj-kondo-hooks/.gitignore +++ b/examples/clj-kondo-hooks/.gitignore @@ -1,5 +1,3 @@ .cpcache .clj-kondo/.cache -.clj-kondo/imports -.clj-kondo/metosin output/actual-output diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index 4f8366cd..2226e780 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -1,6 +1,6 @@ (ns compojure-api-example.clj-kondo-hooks (:require ;[compojure.api.sweet :as sweet] - [compojure.api.sweet :as core] + [compojure.api.core :as core] [ring.util.http-response :as resp])) (core/GET "/30" [] (resp/ok {:result 30})) From 252584ca1c08ba56f4708b9dcc729b0e9402bf87 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 14:06:34 -0500 Subject: [PATCH 14/39] wip --- .../compojure-api/compojure/api/common.clj | 84 +++++++++++++++++++ .../compojure-api/compojure/api/core.clj | 68 ++++++++++++--- .../compojure-api/compojure/api/common.clj | 84 +++++++++++++++++++ .../compojure-api/compojure/api/core.clj | 68 ++++++++++++--- scripts/regen-kondo.clj | 7 +- 5 files changed, 286 insertions(+), 25 deletions(-) create mode 100644 examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj new file mode 100644 index 00000000..23951dd5 --- /dev/null +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj @@ -0,0 +1,84 @@ +(ns compojure.api.common + #?@(:bb [] + :default [(:require [linked.core :as linked])])) + +(defn plain-map? + "checks whether input is a map, but not a record" + [x] (and (map? x) (not (record? x)))) + +(defn extract-parameters + "Extract parameters from head of the list. Parameters can be: + + 1. a map (if followed by any form) `[{:a 1 :b 2} :body]` => `{:a 1 :b 2}` + 2. list of keywords & values `[:a 1 :b 2 :body]` => `{:a 1 :b 2}` + 3. else => `{}` + + Returns a tuple with parameters and body without the parameters" + [c expect-body] + (cond + (and (plain-map? (first c)) (or (not expect-body) (seq (rest c)))) + [(first c) (seq (rest c))] + + (keyword? (first c)) + (let [parameters (->> c + (partition 2) + (take-while (comp keyword? first)) + (mapcat identity) + (apply array-map)) + form (drop (* 2 (count parameters)) c)] + [parameters (seq form)]) + + :else + [{} (seq c)])) + +(defn group-with + "Groups a sequence with predicate returning a tuple of sequences." + [pred coll] + [(seq (filter pred coll)) + (seq (remove pred coll))]) + +(defn merge-vector + "Merges vector elements, optimized for 1 arity (x10 faster than merge)." + [v] + (if (get v 1) + (apply merge v) + (get v 0))) + +(defn fast-map-merge + [x y] + (reduce-kv + (fn [m k v] + (assoc m k v)) + x + y)) + +#?(:bb nil + :default +(defn fifo-memoize [f size] + "Returns a memoized version of a referentially transparent f. The + memoized version of the function keeps a cache of the mapping from arguments + to results and, when calls with the same arguments are repeated often, has + higher performance at the expense of higher memory use. FIFO with size entries." + (let [cache (atom (linked/map))] + (fn [& xs] + (or (@cache xs) + (let [value (apply f xs)] + (swap! cache (fn [mem] + (let [mem (assoc mem xs value)] + (if (>= (count mem) size) + (dissoc mem (-> mem first first)) + mem)))) + value))))) +) + +;; NB: when-ns eats all exceptions inside the body, including those about +;; unresolvable symbols. Keep this in mind when debugging the definitions below. + +(defmacro when-ns [ns & body] + `(try + (eval + '(do + (require ~ns) + ~@body)) + (catch Exception ~'_))) + diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj index bdded967..74db7e9e 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj @@ -1,7 +1,27 @@ +;; :bb reader feature assumes clj-kondo (ns compojure.api.core (:require [compojure.api.meta :as meta] - [compojure.api.routes :as-alias routes] - [compojure.api.middleware :as-alias mw])) + #?@(:bb [] + :default [[compojure.api.async] + [compojure.core :as compojure]]) + [compojure.api.routes #?(:bb :as-alias :default :as) routes] + [compojure.api.middleware #?(:bb :as-alias :default :as) mw])) + +(defn ring-handler + "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" + [handler] + (fn + ([request] (handler request)) + ([request respond raise] (handler request respond raise)))) + +(defn routes + "Create a Ring handler by combining several handlers into one." + [& handlers] + #?(:bb (throw (ex-info "Not supported in bb")) + :default (let [handlers (seq (keep identity (flatten handlers)))] + (routes/map->Route + {:childs (vec handlers) + :handler (meta/routing handlers)})))) (defmacro defroutes "Define a Ring handler function from a sequence of routes. @@ -19,6 +39,14 @@ [bindings & body] `(let ~bindings (routes ~@body))) +(defn undocumented + "Routes without route-documentation. Can be used to wrap routes, + not satisfying compojure.api.routes/Routing -protocol." + [& handlers] + #?(:bb (throw (ex-info "Not supported in bb")) + :default (let [handlers (keep identity handlers)] + (routes/map->Route {:handler (meta/routing handlers)})))) + (defmacro middleware "Wraps routes with given middlewares using thread-first macro. @@ -29,17 +57,35 @@ :deprecated "1.1.14" :superseded-by "route-middleware"} [middleware & body] + (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) + (println (str "compojure.api.core.middleware is deprecated because of security issues. " + "Please use route-middleware instead. middleware will be disabled in a future release." + "Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning."))) `(let [body# (routes ~@body) wrap-mw# (mw/compose-middleware ~middleware)] (routes/create nil nil {} [body#] (wrap-mw# body#)))) -(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env})) +(defn route-middleware + "Wraps routes with given middleware using thread-first macro." + {:style/indent 1 + :supercedes "middleware"} + [middleware & body] + #?(:bb (throw (ex-info "Not supported in bb")) + :default + (let [handler (apply routes body) + x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] + ;; use original handler for docs and wrapped handler for implementation + (routes/map->Route + {:childs [handler] + :handler x-handler})))) + +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:bb true :default false)})) -(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil)) -(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil)) -(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil)) -(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil)) -(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil)) -(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args nil)) -(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil)) -(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil)) +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:bb {:kondo-rule? true} :default nil))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj new file mode 100644 index 00000000..23951dd5 --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj @@ -0,0 +1,84 @@ +(ns compojure.api.common + #?@(:bb [] + :default [(:require [linked.core :as linked])])) + +(defn plain-map? + "checks whether input is a map, but not a record" + [x] (and (map? x) (not (record? x)))) + +(defn extract-parameters + "Extract parameters from head of the list. Parameters can be: + + 1. a map (if followed by any form) `[{:a 1 :b 2} :body]` => `{:a 1 :b 2}` + 2. list of keywords & values `[:a 1 :b 2 :body]` => `{:a 1 :b 2}` + 3. else => `{}` + + Returns a tuple with parameters and body without the parameters" + [c expect-body] + (cond + (and (plain-map? (first c)) (or (not expect-body) (seq (rest c)))) + [(first c) (seq (rest c))] + + (keyword? (first c)) + (let [parameters (->> c + (partition 2) + (take-while (comp keyword? first)) + (mapcat identity) + (apply array-map)) + form (drop (* 2 (count parameters)) c)] + [parameters (seq form)]) + + :else + [{} (seq c)])) + +(defn group-with + "Groups a sequence with predicate returning a tuple of sequences." + [pred coll] + [(seq (filter pred coll)) + (seq (remove pred coll))]) + +(defn merge-vector + "Merges vector elements, optimized for 1 arity (x10 faster than merge)." + [v] + (if (get v 1) + (apply merge v) + (get v 0))) + +(defn fast-map-merge + [x y] + (reduce-kv + (fn [m k v] + (assoc m k v)) + x + y)) + +#?(:bb nil + :default +(defn fifo-memoize [f size] + "Returns a memoized version of a referentially transparent f. The + memoized version of the function keeps a cache of the mapping from arguments + to results and, when calls with the same arguments are repeated often, has + higher performance at the expense of higher memory use. FIFO with size entries." + (let [cache (atom (linked/map))] + (fn [& xs] + (or (@cache xs) + (let [value (apply f xs)] + (swap! cache (fn [mem] + (let [mem (assoc mem xs value)] + (if (>= (count mem) size) + (dissoc mem (-> mem first first)) + mem)))) + value))))) +) + +;; NB: when-ns eats all exceptions inside the body, including those about +;; unresolvable symbols. Keep this in mind when debugging the definitions below. + +(defmacro when-ns [ns & body] + `(try + (eval + '(do + (require ~ns) + ~@body)) + (catch Exception ~'_))) + diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj index bdded967..74db7e9e 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj @@ -1,7 +1,27 @@ +;; :bb reader feature assumes clj-kondo (ns compojure.api.core (:require [compojure.api.meta :as meta] - [compojure.api.routes :as-alias routes] - [compojure.api.middleware :as-alias mw])) + #?@(:bb [] + :default [[compojure.api.async] + [compojure.core :as compojure]]) + [compojure.api.routes #?(:bb :as-alias :default :as) routes] + [compojure.api.middleware #?(:bb :as-alias :default :as) mw])) + +(defn ring-handler + "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" + [handler] + (fn + ([request] (handler request)) + ([request respond raise] (handler request respond raise)))) + +(defn routes + "Create a Ring handler by combining several handlers into one." + [& handlers] + #?(:bb (throw (ex-info "Not supported in bb")) + :default (let [handlers (seq (keep identity (flatten handlers)))] + (routes/map->Route + {:childs (vec handlers) + :handler (meta/routing handlers)})))) (defmacro defroutes "Define a Ring handler function from a sequence of routes. @@ -19,6 +39,14 @@ [bindings & body] `(let ~bindings (routes ~@body))) +(defn undocumented + "Routes without route-documentation. Can be used to wrap routes, + not satisfying compojure.api.routes/Routing -protocol." + [& handlers] + #?(:bb (throw (ex-info "Not supported in bb")) + :default (let [handlers (keep identity handlers)] + (routes/map->Route {:handler (meta/routing handlers)})))) + (defmacro middleware "Wraps routes with given middlewares using thread-first macro. @@ -29,17 +57,35 @@ :deprecated "1.1.14" :superseded-by "route-middleware"} [middleware & body] + (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) + (println (str "compojure.api.core.middleware is deprecated because of security issues. " + "Please use route-middleware instead. middleware will be disabled in a future release." + "Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning."))) `(let [body# (routes ~@body) wrap-mw# (mw/compose-middleware ~middleware)] (routes/create nil nil {} [body#] (wrap-mw# body#)))) -(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env})) +(defn route-middleware + "Wraps routes with given middleware using thread-first macro." + {:style/indent 1 + :supercedes "middleware"} + [middleware & body] + #?(:bb (throw (ex-info "Not supported in bb")) + :default + (let [handler (apply routes body) + x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] + ;; use original handler for docs and wrapped handler for implementation + (routes/map->Route + {:childs [handler] + :handler x-handler})))) + +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:bb true :default false)})) -(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil)) -(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil)) -(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil)) -(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil)) -(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil)) -(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args nil)) -(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil)) -(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil)) +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:bb {:kondo-rule? true} :default nil))) diff --git a/scripts/regen-kondo.clj b/scripts/regen-kondo.clj index 27fa9b5c..948d67ff 100755 --- a/scripts/regen-kondo.clj +++ b/scripts/regen-kondo.clj @@ -1,10 +1,11 @@ #!/bin/bash -set -e +set -ex -bb -f ./script/regen_kondo_config.clj +# bb -f ./script/regen_kondo_config.clj mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure/api -;; rename to .clj +# rename to .clj cp src/compojure/api/common.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj +cp src/compojure/api/core.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj From 69e3de32357dca20a4d7756c14b69dff9bf93221 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 14:14:04 -0500 Subject: [PATCH 15/39] mv --- .../.clj-kondo/imports/metosin/compojure-api}/config.edn | 1 + .../clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn | 1 + examples/clj-kondo-hooks/script/lint | 2 +- resources/clj-kondo.exports/metosin/compojure-api/config.edn | 5 +++++ 4 files changed, 8 insertions(+), 1 deletion(-) rename {resources/clj-kondo.exports/metosin => examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api}/config.edn (73%) create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/config.edn diff --git a/resources/clj-kondo.exports/metosin/config.edn b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn similarity index 73% rename from resources/clj-kondo.exports/metosin/config.edn rename to examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn index 8a5a09f5..45b2fddd 100644 --- a/resources/clj-kondo.exports/metosin/config.edn +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn @@ -1,4 +1,5 @@ {:hooks {:macroexpand {compojure.api.sweet/GET compojure.api.core/GET + compojure.api.core/GET compojure.api.core/GET compojure.api.core/defroutes compojure.api.core/defroutes}}} diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn index 8a5a09f5..45b2fddd 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/config.edn @@ -1,4 +1,5 @@ {:hooks {:macroexpand {compojure.api.sweet/GET compojure.api.core/GET + compojure.api.core/GET compojure.api.core/GET compojure.api.core/defroutes compojure.api.core/defroutes}}} diff --git a/examples/clj-kondo-hooks/script/lint b/examples/clj-kondo-hooks/script/lint index d98c0a94..f3629a0e 100755 --- a/examples/clj-kondo-hooks/script/lint +++ b/examples/clj-kondo-hooks/script/lint @@ -1,3 +1,3 @@ #!/bin/bash -./script/clj-kondo --lint src --config '{:output {:format :text :summary false}}' +./script/clj-kondo --lint src --config '{:output {:format :text :summary false}}' --debug diff --git a/resources/clj-kondo.exports/metosin/compojure-api/config.edn b/resources/clj-kondo.exports/metosin/compojure-api/config.edn new file mode 100644 index 00000000..45b2fddd --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/config.edn @@ -0,0 +1,5 @@ +{:hooks + {:macroexpand + {compojure.api.sweet/GET compojure.api.core/GET + compojure.api.core/GET compojure.api.core/GET + compojure.api.core/defroutes compojure.api.core/defroutes}}} From 8c70bdc85120e6fcf28aaf43e23cb8285b931e4d Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 14:18:54 -0500 Subject: [PATCH 16/39] wip --- .../compojure-api/compojure/api/meta.clj | 309 ------------------ .../metosin/compojure-api/config.edn | 5 - scripts/regen-kondo.clj | 2 + 3 files changed, 2 insertions(+), 314 deletions(-) delete mode 100644 resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj delete mode 100644 resources/clj-kondo.exports/metosin/compojure-api/config.edn diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj deleted file mode 100644 index 6a96a976..00000000 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ /dev/null @@ -1,309 +0,0 @@ -(ns compojure.api.meta - (:require [compojure.api.common :as common :refer [extract-parameters]] - [compojure.api.middleware :as-alias mw] - [compojure.api.routes :as-alias routes] - [plumbing.core :as-alias p] - [plumbing.fnk.impl :as-alias fnk-impl] - [ring.swagger.common :as-alias rsc] - ;[ring.swagger.json-schema :as js] - [schema.core :as-alias s] - [schema-tools.core :as-alias st] - [compojure.api.coerce :as-alias coerce] - compojure.core)) - -(def +compojure-api-request+ - "lexically bound ring-request for handlers." - '+compojure-api-request+) - -;; -;; Schema -;; - -(defn strict [schema] - (dissoc schema 'schema.core/Keyword)) - -(defn fnk-schema [bind] - (->> - (:input-schema - (fnk-impl/letk-input-schema-and-body-form - nil (with-meta bind {:schema s/Any}) [] nil)) - reverse - (into {}))) - -(s/defn src-coerce! - "Return source code for coerce! for a schema with coercion type, - extracted from a key in a ring request." - [schema, key, type :- mw/CoercionType] - (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) - `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) - -(defn- convert-return [schema] - {200 {:schema schema - ;:description (or (js/json-schema-meta schema) "") - }}) - -;; -;; Extension point -;; - -(defmulti restructure-param - "Restructures a key value pair in smart routes. By default the key - is consumed form the :parameters map in acc. k = given key, v = value." - (fn [k v acc] k)) - -;; -;; Pass-through swagger metadata -;; - -(defmethod restructure-param :summary [k v acc] - (update-in acc [:swagger] assoc k v)) - -(defmethod restructure-param :description [k v acc] - (update-in acc [:swagger] assoc k v)) - -(defmethod restructure-param :operationId [k v acc] - (update-in acc [:swagger] assoc k v)) - -(defmethod restructure-param :consumes [k v acc] - (update-in acc [:swagger] assoc k v)) - -(defmethod restructure-param :produces [k v acc] - (update-in acc [:swagger] assoc k v)) - -;; -;; Smart restructurings -;; - -; Boolean to discard the route out from api documentation -; Example: -; :no-doc true -(defmethod restructure-param :no-doc [_ v acc] - (update-in acc [:swagger] assoc :x-no-doc v)) - -; publishes the data as swagger-parameters without any side-effects / coercion. -; Examples: -; :swagger {:responses {200 {:schema User} -; 404 {:schema Error -; :description "Not Found"} } -; :parameters {:query {:q s/Str} -; :body NewUser}}} -(defmethod restructure-param :swagger [_ swagger acc] - (assoc-in acc [:swagger :swagger] swagger)) - -; Route name, used with path-for -; Example: -; :name :user-route -(defmethod restructure-param :name [_ v acc] - (update-in acc [:swagger] assoc :x-name v)) - -; Tags for api categorization. Ignores duplicates. -; Examples: -; :tags [:admin] -(defmethod restructure-param :tags [_ tags acc] - (update-in acc [:swagger :tags] (comp set into) tags)) - -; Defines a return type and coerces the return value of a body against it. -; Examples: -; :return MySchema -; :return {:value String} -; :return #{{:key (s/maybe Long)}} -(defmethod restructure-param :return [_ schema acc] - (let [response (convert-return schema)] - (-> acc - (update-in [:swagger :responses] (fnil conj []) response) - (update-in [:responses] (fnil conj []) response)))) - -; value is a map of http-response-code -> Schema. Translates to both swagger -; parameters and return schema coercion. Schemas can be decorated with meta-data. -; Examples: -; :responses {403 nil} -; :responses {403 {:schema ErrorEnvelope}} -; :responses {403 {:schema ErrorEnvelope, :description \"Underflow\"}} -(defmethod restructure-param :responses [_ responses acc] - (-> acc - (update-in [:swagger :responses] (fnil conj []) responses) - (update-in [:responses] (fnil conj []) responses))) - -; reads body-params into a enhanced let. First parameter is the let symbol, -; second is the Schema to be coerced! against. -; Examples: -; :body [user User] -(defmethod restructure-param :body [_ [value schema :as bv] acc] - (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) - (assert (= 2 (count bv)) - (str ":body should be [sym schema], provided: " bv - "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true"))) - (-> acc - (update-in [:lets] into [value (src-coerce! schema :body-params :body)]) - (assoc-in [:swagger :parameters :body] schema))) - -; reads query-params into a enhanced let. First parameter is the let symbol, -; second is the Schema to be coerced! against. -; Examples: -; :query [user User] -(defmethod restructure-param :query [_ [value schema :as bv] acc] - (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) - (assert (= 2 (count bv)) - (str ":query should be [sym schema], provided: " bv - "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true"))) - (-> acc - (update-in [:lets] into [value (src-coerce! schema :query-params :string)]) - (assoc-in [:swagger :parameters :query] schema))) - -; reads header-params into a enhanced let. First parameter is the let symbol, -; second is the Schema to be coerced! against. -; Examples: -; :headers [headers Headers] -(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"))) - - (-> acc - (update-in [:lets] into [value (src-coerce! schema :headers :string)]) - (assoc-in [:swagger :parameters :header] schema))) - -; restructures body-params with plumbing letk notation. Example: -; :body-params [id :- Long name :- String] -(defmethod restructure-param :body-params [_ body-params acc] - (let [schema (strict (fnk-schema body-params))] - (-> acc - (update-in [:letks] into [body-params (src-coerce! schema :body-params :body)]) - (assoc-in [:swagger :parameters :body] schema)))) - -; restructures form-params with plumbing letk notation. Example: -; :form-params [id :- Long name :- String] -(defmethod restructure-param :form-params [_ form-params acc] - (let [schema (strict (fnk-schema form-params))] - (-> acc - (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) - (update-in [:swagger :parameters :formData] st/merge schema) - (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) - -; restructures multipart-params with plumbing letk notation and consumes "multipart/form-data" -; :multipart-params [file :- compojure.api.upload/TempFileUpload] -(defmethod restructure-param :multipart-params [_ params acc] - (let [schema (strict (fnk-schema params))] - (-> acc - (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) - (update-in [:swagger :parameters :formData] st/merge schema) - (assoc-in [:swagger :consumes] ["multipart/form-data"])))) - -; restructures header-params with plumbing letk notation. Example: -; :header-params [id :- Long name :- String] -(defmethod restructure-param :header-params [_ header-params acc] - (let [schema (fnk-schema header-params)] - (-> acc - (update-in [:letks] into [header-params (src-coerce! schema :headers :string)]) - (assoc-in [:swagger :parameters :header] schema)))) - -; restructures query-params with plumbing letk notation. Example: -; :query-params [id :- Long name :- String] -(defmethod restructure-param :query-params [_ query-params acc] - (let [schema (fnk-schema query-params)] - (-> acc - (update-in [:letks] into [query-params (src-coerce! schema :query-params :string)]) - (assoc-in [:swagger :parameters :query] schema)))) - -; restructures path-params by plumbing letk notation. Example: -; :path-params [id :- Long name :- String] -(defmethod restructure-param :path-params [_ path-params acc] - (let [schema (fnk-schema path-params)] - (-> acc - (update-in [:letks] into [path-params (src-coerce! schema :route-params :string)]) - (assoc-in [:swagger :parameters :path] schema)))) - -; Applies the given vector of middlewares to the route -(defmethod restructure-param :middleware [_ middleware acc] - (update-in acc [:middleware] into middleware)) - -; Bind to stuff in request components using letk syntax -(defmethod restructure-param :components [_ components acc] - (update-in acc [:letks] into [components `(mw/get-components ~+compojure-api-request+)])) - -; route-specific override for coercers -(defmethod restructure-param :coercion [_ coercion acc] - (update-in acc [:middleware] conj [mw/wrap-coercion coercion])) - -;; -;; Api -;; - -(defn- destructure-compojure-api-request - "Returns a vector of four elements: - - pruned path string - - new lets list - - bindings form for compojure route - - symbol to which request will be bound" - [path arg] - (let [path-string (if (vector? path) (first path) path)] - (cond - ;; GET "/route" [] - (vector? arg) [path-string [] (into arg [:as +compojure-api-request+]) +compojure-api-request+] - ;; GET "/route" {:as req} - (map? arg) (if-let [as (:as arg)] - [path-string [+compojure-api-request+ as] arg as] - [path-string [] (merge arg [:as +compojure-api-request+]) +compojure-api-request+]) - ;; GET "/route" req - (symbol? arg) [path-string [+compojure-api-request+ arg] arg arg] - :else (throw - (RuntimeException. - (str "unknown compojure destruction syntax: " arg)))))) - -(defn merge-parameters - "Merge parameters at runtime to allow usage of runtime-paramers with route-macros." - [{:keys [responses swagger] :as parameters}] - (cond-> parameters - ;(seq responses) (assoc :responses (common/merge-vector responses)) - ;swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)) - )) - -(defn restructure [method [path arg & args] {:keys [context?]}] - (let [[options body] (extract-parameters args true) - [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) - - {:keys [lets - letks - responses - middleware - middlewares - swagger - parameters - body]} (reduce - (fn [acc [k v]] - (restructure-param k v (update-in acc [:parameters] dissoc k))) - {:lets lets - :letks [] - :responses nil - :middleware [] - :swagger {} - :body body} - options) - - ;; migration helpers - _ (assert (not middlewares) ":middlewares is deprecated with 1.0.0, use :middleware instead.") - _ (assert (not parameters) ":parameters is deprecated with 1.0.0, use :swagger instead.") - - ;; response coercion middleware, why not just code? - middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] - - (if context? - - ;; context - (let [form `(compojure.core/routes ~@body) - form (if (seq letks) `(p/letk ~letks ~form) form) - form (if (seq lets) `(let ~lets ~form) form) - form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) - form `(compojure.core/context ~path ~arg-with-request ~form)] - - `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form)) - - ;; endpoints - (let [form `(do ~@body) - form (if (seq letks) `(p/letk ~letks ~form) form) - form (if (seq lets) `(let ~lets ~form) form) - form (compojure.core/compile-route method path arg-with-request (list form)) - form (if (seq middleware) `(compojure.core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] - - `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/config.edn b/resources/clj-kondo.exports/metosin/compojure-api/config.edn deleted file mode 100644 index 45b2fddd..00000000 --- a/resources/clj-kondo.exports/metosin/compojure-api/config.edn +++ /dev/null @@ -1,5 +0,0 @@ -{:hooks - {:macroexpand - {compojure.api.sweet/GET compojure.api.core/GET - compojure.api.core/GET compojure.api.core/GET - compojure.api.core/defroutes compojure.api.core/defroutes}}} diff --git a/scripts/regen-kondo.clj b/scripts/regen-kondo.clj index 948d67ff..6893e944 100755 --- a/scripts/regen-kondo.clj +++ b/scripts/regen-kondo.clj @@ -4,8 +4,10 @@ set -ex # bb -f ./script/regen_kondo_config.clj +rm -r resources/clj-kondo.exports mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure/api + # rename to .clj cp src/compojure/api/common.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj cp src/compojure/api/core.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj From 26580852b4f564575f360c62c89390bade12e3a3 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 14:22:41 -0500 Subject: [PATCH 17/39] gen --- .../metosin/compojure-api/config.edn | 1 + scripts/regen-kondo.clj | 3 +-- scripts/regen_kondo_config.clj | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/config.edn create mode 100755 scripts/regen_kondo_config.clj diff --git a/resources/clj-kondo.exports/metosin/compojure-api/config.edn b/resources/clj-kondo.exports/metosin/compojure-api/config.edn new file mode 100644 index 00000000..df66b977 --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/config.edn @@ -0,0 +1 @@ +{:hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}} \ No newline at end of file diff --git a/scripts/regen-kondo.clj b/scripts/regen-kondo.clj index 6893e944..58d98986 100755 --- a/scripts/regen-kondo.clj +++ b/scripts/regen-kondo.clj @@ -2,11 +2,10 @@ set -ex -# bb -f ./script/regen_kondo_config.clj - rm -r resources/clj-kondo.exports mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure/api +bb -f ./scripts/regen_kondo_config.clj # rename to .clj cp src/compojure/api/common.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj diff --git a/scripts/regen_kondo_config.clj b/scripts/regen_kondo_config.clj new file mode 100755 index 00000000..8cc9f77e --- /dev/null +++ b/scripts/regen_kondo_config.clj @@ -0,0 +1,11 @@ +#!/usr/bin/env bb + +(ns regen-kondo-config) + +(defn -main [& args] + (spit "resources/clj-kondo.exports/metosin/compojure-api/config.edn" + '{:hooks + {:macroexpand + {compojure.api.core/GET compojure.api.core/GET}}})) + +(when (= *file* (System/getProperty "babashka.file")) (-main)) From 34670f216393e0d3ecd53ee9fa3c5a95763fcb9a Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 14:25:11 -0500 Subject: [PATCH 18/39] wip --- .../compojure-api/compojure/api/meta.clj | 112 ++++- .../imports/metosin/compojure-api/config.edn | 6 +- .../compojure-api/compojure/api/meta.clj | 381 ++++++++++++++++++ scripts/regen-kondo.clj | 1 + 4 files changed, 475 insertions(+), 25 deletions(-) create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index 6a96a976..fee947cf 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -1,20 +1,50 @@ (ns compojure.api.meta (:require [compojure.api.common :as common :refer [extract-parameters]] - [compojure.api.middleware :as-alias mw] - [compojure.api.routes :as-alias routes] - [plumbing.core :as-alias p] - [plumbing.fnk.impl :as-alias fnk-impl] - [ring.swagger.common :as-alias rsc] - ;[ring.swagger.json-schema :as js] - [schema.core :as-alias s] - [schema-tools.core :as-alias st] - [compojure.api.coerce :as-alias coerce] + [compojure.api.middleware :as mw] + [compojure.api.routes :as routes] + [plumbing.core :as p] + [plumbing.fnk.impl :as fnk-impl] + [ring.swagger.common :as rsc] + [ring.swagger.json-schema :as js] + [schema.core :as s] + [schema-tools.core :as st] + [compojure.api.coerce :as coerce] compojure.core)) (def +compojure-api-request+ "lexically bound ring-request for handlers." '+compojure-api-request+) +;; https://github.com/clojure/tools.macro/blob/415512648bb51153f380823c41323cda2c13f47f/src/main/clojure/clojure/tools/macro.clj +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (https://opensource.org/license/epl-1-0/) +;; which can be found in the file epl-v10.html at the root of this distribution. By using this software in any fashion, you are agreeing to +;; be bound bythe terms of this license. You must not remove this notice, or any other, from this software. +(defn name-with-attributes + "To be used in macro definitions. + Handles optional docstrings and attribute maps for a name to be defined + in a list of macro arguments. If the first macro argument is a string, + it is added as a docstring to name and removed from the macro argument + list. If afterwards the first macro argument is a map, its entries are + added to the name's metadata map and the map is removed from the + macro argument list. The return value is a vector containing the name + with its extended metadata map and the list of unprocessed macro + arguments." + [name macro-args] + (let [[docstring macro-args] (if (string? (first macro-args)) + [(first macro-args) (next macro-args)] + [nil macro-args]) + [attr macro-args] (if (map? (first macro-args)) + [(first macro-args) (next macro-args)] + [{} macro-args]) + attr (if docstring + (assoc attr :doc docstring) + attr) + attr (if (meta name) + (conj (meta name) attr) + attr)] + [(with-meta name attr) macro-args])) + ;; ;; Schema ;; @@ -39,8 +69,7 @@ (defn- convert-return [schema] {200 {:schema schema - ;:description (or (js/json-schema-meta schema) "") - }}) + :description (or (js/json-schema-meta schema) "")}}) ;; ;; Extension point @@ -226,6 +255,38 @@ (defmethod restructure-param :coercion [_ coercion acc] (update-in acc [:middleware] conj [mw/wrap-coercion coercion])) +;; +;; Impl +;; + +(defmacro dummy-let + "Dummy let-macro used in resolving route-docs. not part of normal invocation chain." + [bindings & body] + (let [bind-form (vec (apply concat (for [n (take-nth 2 bindings)] [n nil])))] + `(let ~bind-form ~@body))) + +(defmacro dummy-letk + "Dummy letk-macro used in resolving route-docs. not part of normal invocation chain." + [bindings & body] + (reduce + (fn [cur-body-form [bind-form]] + (if (symbol? bind-form) + `(let [~bind-form nil] ~cur-body-form) + (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form + &env + (fnk-impl/ensure-schema-metadata &env bind-form) + [] + cur-body-form) + body-form (clojure.walk/prewalk-replace {'plumbing.fnk.schema/safe-get 'clojure.core/get} body-form)] + `(let [~map-sym nil] ~body-form)))) + `(do ~@body) + (reverse (partition 2 bindings)))) + +(defn routing [handlers] + (if-let [handlers (seq (keep identity (flatten handlers)))] + (apply compojure.core/routes handlers) + (fn ([_] nil) ([_ respond _] (respond nil))))) + ;; ;; Api ;; @@ -252,14 +313,15 @@ (str "unknown compojure destruction syntax: " arg)))))) (defn merge-parameters - "Merge parameters at runtime to allow usage of runtime-paramers with route-macros." + "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." [{:keys [responses swagger] :as parameters}] (cond-> parameters - ;(seq responses) (assoc :responses (common/merge-vector responses)) - ;swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)) - )) + (seq responses) (assoc :responses (common/merge-vector responses)) + swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) -(defn restructure [method [path arg & args] {:keys [context?]}] +(defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] + #?(:bb (assert kondo-rule?) + :default nil) (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) @@ -278,7 +340,8 @@ :responses nil :middleware [] :swagger {} - :body body} + :body body + :kondo-rule? kondo-rule?} options) ;; migration helpers @@ -295,9 +358,18 @@ form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) - form `(compojure.core/context ~path ~arg-with-request ~form)] - - `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form)) + form `(compojure.core/context ~path ~arg-with-request ~form) + + ;; create and apply a separate lookup-function to find the inner routes + childs (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(compojure.core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form)] + + `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) ;; endpoints (let [form `(do ~@body) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn index 45b2fddd..df66b977 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn @@ -1,5 +1 @@ -{:hooks - {:macroexpand - {compojure.api.sweet/GET compojure.api.core/GET - compojure.api.core/GET compojure.api.core/GET - compojure.api.core/defroutes compojure.api.core/defroutes}}} +{:hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}} \ No newline at end of file diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj new file mode 100644 index 00000000..fee947cf --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -0,0 +1,381 @@ +(ns compojure.api.meta + (:require [compojure.api.common :as common :refer [extract-parameters]] + [compojure.api.middleware :as mw] + [compojure.api.routes :as routes] + [plumbing.core :as p] + [plumbing.fnk.impl :as fnk-impl] + [ring.swagger.common :as rsc] + [ring.swagger.json-schema :as js] + [schema.core :as s] + [schema-tools.core :as st] + [compojure.api.coerce :as coerce] + compojure.core)) + +(def +compojure-api-request+ + "lexically bound ring-request for handlers." + '+compojure-api-request+) + +;; https://github.com/clojure/tools.macro/blob/415512648bb51153f380823c41323cda2c13f47f/src/main/clojure/clojure/tools/macro.clj +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (https://opensource.org/license/epl-1-0/) +;; which can be found in the file epl-v10.html at the root of this distribution. By using this software in any fashion, you are agreeing to +;; be bound bythe terms of this license. You must not remove this notice, or any other, from this software. +(defn name-with-attributes + "To be used in macro definitions. + Handles optional docstrings and attribute maps for a name to be defined + in a list of macro arguments. If the first macro argument is a string, + it is added as a docstring to name and removed from the macro argument + list. If afterwards the first macro argument is a map, its entries are + added to the name's metadata map and the map is removed from the + macro argument list. The return value is a vector containing the name + with its extended metadata map and the list of unprocessed macro + arguments." + [name macro-args] + (let [[docstring macro-args] (if (string? (first macro-args)) + [(first macro-args) (next macro-args)] + [nil macro-args]) + [attr macro-args] (if (map? (first macro-args)) + [(first macro-args) (next macro-args)] + [{} macro-args]) + attr (if docstring + (assoc attr :doc docstring) + attr) + attr (if (meta name) + (conj (meta name) attr) + attr)] + [(with-meta name attr) macro-args])) + +;; +;; Schema +;; + +(defn strict [schema] + (dissoc schema 'schema.core/Keyword)) + +(defn fnk-schema [bind] + (->> + (:input-schema + (fnk-impl/letk-input-schema-and-body-form + nil (with-meta bind {:schema s/Any}) [] nil)) + reverse + (into {}))) + +(s/defn src-coerce! + "Return source code for coerce! for a schema with coercion type, + extracted from a key in a ring request." + [schema, key, type :- mw/CoercionType] + (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) + `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) + +(defn- convert-return [schema] + {200 {:schema schema + :description (or (js/json-schema-meta schema) "")}}) + +;; +;; Extension point +;; + +(defmulti restructure-param + "Restructures a key value pair in smart routes. By default the key + is consumed form the :parameters map in acc. k = given key, v = value." + (fn [k v acc] k)) + +;; +;; Pass-through swagger metadata +;; + +(defmethod restructure-param :summary [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :description [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :operationId [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :consumes [k v acc] + (update-in acc [:swagger] assoc k v)) + +(defmethod restructure-param :produces [k v acc] + (update-in acc [:swagger] assoc k v)) + +;; +;; Smart restructurings +;; + +; Boolean to discard the route out from api documentation +; Example: +; :no-doc true +(defmethod restructure-param :no-doc [_ v acc] + (update-in acc [:swagger] assoc :x-no-doc v)) + +; publishes the data as swagger-parameters without any side-effects / coercion. +; Examples: +; :swagger {:responses {200 {:schema User} +; 404 {:schema Error +; :description "Not Found"} } +; :parameters {:query {:q s/Str} +; :body NewUser}}} +(defmethod restructure-param :swagger [_ swagger acc] + (assoc-in acc [:swagger :swagger] swagger)) + +; Route name, used with path-for +; Example: +; :name :user-route +(defmethod restructure-param :name [_ v acc] + (update-in acc [:swagger] assoc :x-name v)) + +; Tags for api categorization. Ignores duplicates. +; Examples: +; :tags [:admin] +(defmethod restructure-param :tags [_ tags acc] + (update-in acc [:swagger :tags] (comp set into) tags)) + +; Defines a return type and coerces the return value of a body against it. +; Examples: +; :return MySchema +; :return {:value String} +; :return #{{:key (s/maybe Long)}} +(defmethod restructure-param :return [_ schema acc] + (let [response (convert-return schema)] + (-> acc + (update-in [:swagger :responses] (fnil conj []) response) + (update-in [:responses] (fnil conj []) response)))) + +; value is a map of http-response-code -> Schema. Translates to both swagger +; parameters and return schema coercion. Schemas can be decorated with meta-data. +; Examples: +; :responses {403 nil} +; :responses {403 {:schema ErrorEnvelope}} +; :responses {403 {:schema ErrorEnvelope, :description \"Underflow\"}} +(defmethod restructure-param :responses [_ responses acc] + (-> acc + (update-in [:swagger :responses] (fnil conj []) responses) + (update-in [:responses] (fnil conj []) responses))) + +; reads body-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :body [user User] +(defmethod restructure-param :body [_ [value schema :as bv] acc] + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) + (assert (= 2 (count bv)) + (str ":body should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true"))) + (-> acc + (update-in [:lets] into [value (src-coerce! schema :body-params :body)]) + (assoc-in [:swagger :parameters :body] schema))) + +; reads query-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :query [user User] +(defmethod restructure-param :query [_ [value schema :as bv] acc] + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) + (assert (= 2 (count bv)) + (str ":query should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true"))) + (-> acc + (update-in [:lets] into [value (src-coerce! schema :query-params :string)]) + (assoc-in [:swagger :parameters :query] schema))) + +; reads header-params into a enhanced let. First parameter is the let symbol, +; second is the Schema to be coerced! against. +; Examples: +; :headers [headers Headers] +(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"))) + + (-> acc + (update-in [:lets] into [value (src-coerce! schema :headers :string)]) + (assoc-in [:swagger :parameters :header] schema))) + +; restructures body-params with plumbing letk notation. Example: +; :body-params [id :- Long name :- String] +(defmethod restructure-param :body-params [_ body-params acc] + (let [schema (strict (fnk-schema body-params))] + (-> acc + (update-in [:letks] into [body-params (src-coerce! schema :body-params :body)]) + (assoc-in [:swagger :parameters :body] schema)))) + +; restructures form-params with plumbing letk notation. Example: +; :form-params [id :- Long name :- String] +(defmethod restructure-param :form-params [_ form-params acc] + (let [schema (strict (fnk-schema form-params))] + (-> acc + (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) + (update-in [:swagger :parameters :formData] st/merge schema) + (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) + +; restructures multipart-params with plumbing letk notation and consumes "multipart/form-data" +; :multipart-params [file :- compojure.api.upload/TempFileUpload] +(defmethod restructure-param :multipart-params [_ params acc] + (let [schema (strict (fnk-schema params))] + (-> acc + (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) + (update-in [:swagger :parameters :formData] st/merge schema) + (assoc-in [:swagger :consumes] ["multipart/form-data"])))) + +; restructures header-params with plumbing letk notation. Example: +; :header-params [id :- Long name :- String] +(defmethod restructure-param :header-params [_ header-params acc] + (let [schema (fnk-schema header-params)] + (-> acc + (update-in [:letks] into [header-params (src-coerce! schema :headers :string)]) + (assoc-in [:swagger :parameters :header] schema)))) + +; restructures query-params with plumbing letk notation. Example: +; :query-params [id :- Long name :- String] +(defmethod restructure-param :query-params [_ query-params acc] + (let [schema (fnk-schema query-params)] + (-> acc + (update-in [:letks] into [query-params (src-coerce! schema :query-params :string)]) + (assoc-in [:swagger :parameters :query] schema)))) + +; restructures path-params by plumbing letk notation. Example: +; :path-params [id :- Long name :- String] +(defmethod restructure-param :path-params [_ path-params acc] + (let [schema (fnk-schema path-params)] + (-> acc + (update-in [:letks] into [path-params (src-coerce! schema :route-params :string)]) + (assoc-in [:swagger :parameters :path] schema)))) + +; Applies the given vector of middlewares to the route +(defmethod restructure-param :middleware [_ middleware acc] + (update-in acc [:middleware] into middleware)) + +; Bind to stuff in request components using letk syntax +(defmethod restructure-param :components [_ components acc] + (update-in acc [:letks] into [components `(mw/get-components ~+compojure-api-request+)])) + +; route-specific override for coercers +(defmethod restructure-param :coercion [_ coercion acc] + (update-in acc [:middleware] conj [mw/wrap-coercion coercion])) + +;; +;; Impl +;; + +(defmacro dummy-let + "Dummy let-macro used in resolving route-docs. not part of normal invocation chain." + [bindings & body] + (let [bind-form (vec (apply concat (for [n (take-nth 2 bindings)] [n nil])))] + `(let ~bind-form ~@body))) + +(defmacro dummy-letk + "Dummy letk-macro used in resolving route-docs. not part of normal invocation chain." + [bindings & body] + (reduce + (fn [cur-body-form [bind-form]] + (if (symbol? bind-form) + `(let [~bind-form nil] ~cur-body-form) + (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form + &env + (fnk-impl/ensure-schema-metadata &env bind-form) + [] + cur-body-form) + body-form (clojure.walk/prewalk-replace {'plumbing.fnk.schema/safe-get 'clojure.core/get} body-form)] + `(let [~map-sym nil] ~body-form)))) + `(do ~@body) + (reverse (partition 2 bindings)))) + +(defn routing [handlers] + (if-let [handlers (seq (keep identity (flatten handlers)))] + (apply compojure.core/routes handlers) + (fn ([_] nil) ([_ respond _] (respond nil))))) + +;; +;; Api +;; + +(defn- destructure-compojure-api-request + "Returns a vector of four elements: + - pruned path string + - new lets list + - bindings form for compojure route + - symbol to which request will be bound" + [path arg] + (let [path-string (if (vector? path) (first path) path)] + (cond + ;; GET "/route" [] + (vector? arg) [path-string [] (into arg [:as +compojure-api-request+]) +compojure-api-request+] + ;; GET "/route" {:as req} + (map? arg) (if-let [as (:as arg)] + [path-string [+compojure-api-request+ as] arg as] + [path-string [] (merge arg [:as +compojure-api-request+]) +compojure-api-request+]) + ;; GET "/route" req + (symbol? arg) [path-string [+compojure-api-request+ arg] arg arg] + :else (throw + (RuntimeException. + (str "unknown compojure destruction syntax: " arg)))))) + +(defn merge-parameters + "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." + [{:keys [responses swagger] :as parameters}] + (cond-> parameters + (seq responses) (assoc :responses (common/merge-vector responses)) + swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) + +(defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] + #?(:bb (assert kondo-rule?) + :default nil) + (let [[options body] (extract-parameters args true) + [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) + + {:keys [lets + letks + responses + middleware + middlewares + swagger + parameters + body]} (reduce + (fn [acc [k v]] + (restructure-param k v (update-in acc [:parameters] dissoc k))) + {:lets lets + :letks [] + :responses nil + :middleware [] + :swagger {} + :body body + :kondo-rule? kondo-rule?} + options) + + ;; migration helpers + _ (assert (not middlewares) ":middlewares is deprecated with 1.0.0, use :middleware instead.") + _ (assert (not parameters) ":parameters is deprecated with 1.0.0, use :swagger instead.") + + ;; response coercion middleware, why not just code? + middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] + + (if context? + + ;; context + (let [form `(compojure.core/routes ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) + form `(compojure.core/context ~path ~arg-with-request ~form) + + ;; create and apply a separate lookup-function to find the inner routes + childs (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(compojure.core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form)] + + `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) + + ;; endpoints + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (compojure.core/compile-route method path arg-with-request (list form)) + form (if (seq middleware) `(compojure.core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] + + `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) diff --git a/scripts/regen-kondo.clj b/scripts/regen-kondo.clj index 58d98986..bf3b7771 100755 --- a/scripts/regen-kondo.clj +++ b/scripts/regen-kondo.clj @@ -10,3 +10,4 @@ bb -f ./scripts/regen_kondo_config.clj # rename to .clj cp src/compojure/api/common.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj cp src/compojure/api/core.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj +cp src/compojure/api/meta.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj From 62323bdf8bae21db02121ed629404665bc5f083e Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 15:21:58 -0500 Subject: [PATCH 19/39] gen --- .../expand_kondo_feature.clj | 9 ++++++ project.clj | 7 ++++- .../compojure-api/compojure/api/common.clj | 4 +-- .../compojure-api/compojure/api/core.clj | 31 +++++++++---------- .../compojure-api/compojure/api/meta.clj | 2 +- scripts/regen-kondo.clj | 5 --- scripts/regen_kondo_config.clj | 13 +++++++- src/compojure/api/common.cljc | 4 +-- src/compojure/api/core.cljc | 31 +++++++++---------- src/compojure/api/meta.cljc | 2 +- 10 files changed, 63 insertions(+), 45 deletions(-) create mode 100644 dev/compojure_api_dev/expand_kondo_feature.clj diff --git a/dev/compojure_api_dev/expand_kondo_feature.clj b/dev/compojure_api_dev/expand_kondo_feature.clj new file mode 100644 index 00000000..57c44319 --- /dev/null +++ b/dev/compojure_api_dev/expand_kondo_feature.clj @@ -0,0 +1,9 @@ +(ns compojure-api-dev.expand-kondo-feature) + +(defn visit-forms-in-file [file] + {:pre [(string? file)]} + (spit (str/replace "\n" new-forms) + file)) + +(defn -main [& files] + (run! visit-forms-in-file files)) diff --git a/project.clj b/project.clj index ac12927b..e9cad8a5 100644 --- a/project.clj +++ b/project.clj @@ -47,8 +47,13 @@ [criterium "0.4.5"]] :ring {:handler examples.thingie/app :reload-paths ["src" "examples/thingie/src"]} - :source-paths ["examples/thingie/src" "examples/thingie/dev-src"] + :source-paths ["examples/thingie/src" + "examples/thingie/dev-src" + "dev"] :main examples.server} + :expand-kondo-feature {:source-paths ["dev"] + :dependencies [[org.clojure/tools.reader "1.5.0"] + [org.clojure/tools.namespace "1.5.0"]]} :perf {:jvm-opts ^:replace ["-server" "-Xmx4096m" "-Dclojure.compiler.direct-linking=true"]} diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj index 23951dd5..78a21197 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj @@ -1,5 +1,5 @@ (ns compojure.api.common - #?@(:bb [] + #?@(:default [] :default [(:require [linked.core :as linked])])) (defn plain-map? @@ -52,7 +52,7 @@ x y)) -#?(:bb nil +#?(:default nil :default (defn fifo-memoize [f size] "Returns a memoized version of a referentially transparent f. The diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj index 74db7e9e..5ecbacff 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj @@ -1,11 +1,10 @@ -;; :bb reader feature assumes clj-kondo (ns compojure.api.core (:require [compojure.api.meta :as meta] - #?@(:bb [] + #?@(:default [] :default [[compojure.api.async] [compojure.core :as compojure]]) - [compojure.api.routes #?(:bb :as-alias :default :as) routes] - [compojure.api.middleware #?(:bb :as-alias :default :as) mw])) + [compojure.api.routes #?(:default :as-alias :default :as) routes] + [compojure.api.middleware #?(:default :as-alias :default :as) mw])) (defn ring-handler "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" @@ -17,7 +16,7 @@ (defn routes "Create a Ring handler by combining several handlers into one." [& handlers] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:default (throw (ex-info "Not supported in bb")) :default (let [handlers (seq (keep identity (flatten handlers)))] (routes/map->Route {:childs (vec handlers) @@ -43,7 +42,7 @@ "Routes without route-documentation. Can be used to wrap routes, not satisfying compojure.api.routes/Routing -protocol." [& handlers] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:default (throw (ex-info "Not supported in bb")) :default (let [handlers (keep identity handlers)] (routes/map->Route {:handler (meta/routing handlers)})))) @@ -70,7 +69,7 @@ {:style/indent 1 :supercedes "middleware"} [middleware & body] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:default (throw (ex-info "Not supported in bb")) :default (let [handler (apply routes body) x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] @@ -79,13 +78,13 @@ {:childs [handler] :handler x-handler})))) -(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:bb true :default false)})) +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:default true :default false)})) -(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:default {:kondo-rule? true} :default nil))) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:default {:kondo-rule? true} :default nil))) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:default {:kondo-rule? true} :default nil))) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:default {:kondo-rule? true} :default nil))) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:default {:kondo-rule? true} :default nil))) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:default {:kondo-rule? true} :default nil))) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:default {:kondo-rule? true} :default nil))) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:default {:kondo-rule? true} :default nil))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index fee947cf..ba00d78f 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -320,7 +320,7 @@ swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] - #?(:bb (assert kondo-rule?) + #?(:default (assert kondo-rule?) :default nil) (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) diff --git a/scripts/regen-kondo.clj b/scripts/regen-kondo.clj index bf3b7771..01e946d6 100755 --- a/scripts/regen-kondo.clj +++ b/scripts/regen-kondo.clj @@ -6,8 +6,3 @@ rm -r resources/clj-kondo.exports mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure/api bb -f ./scripts/regen_kondo_config.clj - -# rename to .clj -cp src/compojure/api/common.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj -cp src/compojure/api/core.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj -cp src/compojure/api/meta.cljc resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj diff --git a/scripts/regen_kondo_config.clj b/scripts/regen_kondo_config.clj index 8cc9f77e..0e0a7c9f 100755 --- a/scripts/regen_kondo_config.clj +++ b/scripts/regen_kondo_config.clj @@ -1,8 +1,19 @@ #!/usr/bin/env bb -(ns regen-kondo-config) +(ns regen-kondo-config + (:require [clojure.string :as str])) + +(def renames + ;; rename to .clj + {"src/compojure/api/common.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj" + "src/compojure/api/core.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj" + "src/compojure/api/meta.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj"}) + (defn -main [& args] + (doseq [[from to] renames] + (spit to + (str/replace (slurp from) ":clj-kondo" ":default"))) (spit "resources/clj-kondo.exports/metosin/compojure-api/config.edn" '{:hooks {:macroexpand diff --git a/src/compojure/api/common.cljc b/src/compojure/api/common.cljc index 23951dd5..0e57af50 100644 --- a/src/compojure/api/common.cljc +++ b/src/compojure/api/common.cljc @@ -1,5 +1,5 @@ (ns compojure.api.common - #?@(:bb [] + #?@(:clj-kondo [] :default [(:require [linked.core :as linked])])) (defn plain-map? @@ -52,7 +52,7 @@ x y)) -#?(:bb nil +#?(:clj-kondo nil :default (defn fifo-memoize [f size] "Returns a memoized version of a referentially transparent f. The diff --git a/src/compojure/api/core.cljc b/src/compojure/api/core.cljc index 74db7e9e..2337bd36 100644 --- a/src/compojure/api/core.cljc +++ b/src/compojure/api/core.cljc @@ -1,11 +1,10 @@ -;; :bb reader feature assumes clj-kondo (ns compojure.api.core (:require [compojure.api.meta :as meta] - #?@(:bb [] + #?@(:clj-kondo [] :default [[compojure.api.async] [compojure.core :as compojure]]) - [compojure.api.routes #?(:bb :as-alias :default :as) routes] - [compojure.api.middleware #?(:bb :as-alias :default :as) mw])) + [compojure.api.routes #?(:clj-kondo :as-alias :default :as) routes] + [compojure.api.middleware #?(:clj-kondo :as-alias :default :as) mw])) (defn ring-handler "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" @@ -17,7 +16,7 @@ (defn routes "Create a Ring handler by combining several handlers into one." [& handlers] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:clj-kondo (throw (ex-info "Not supported in bb")) :default (let [handlers (seq (keep identity (flatten handlers)))] (routes/map->Route {:childs (vec handlers) @@ -43,7 +42,7 @@ "Routes without route-documentation. Can be used to wrap routes, not satisfying compojure.api.routes/Routing -protocol." [& handlers] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:clj-kondo (throw (ex-info "Not supported in bb")) :default (let [handlers (keep identity handlers)] (routes/map->Route {:handler (meta/routing handlers)})))) @@ -70,7 +69,7 @@ {:style/indent 1 :supercedes "middleware"} [middleware & body] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:clj-kondo (throw (ex-info "Not supported in bb")) :default (let [handler (apply routes body) x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] @@ -79,13 +78,13 @@ {:childs [handler] :handler x-handler})))) -(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:bb true :default false)})) +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:clj-kondo true :default false)})) -(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:clj-kondo {:kondo-rule? true} :default nil))) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:clj-kondo {:kondo-rule? true} :default nil))) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:clj-kondo {:kondo-rule? true} :default nil))) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:clj-kondo {:kondo-rule? true} :default nil))) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:clj-kondo {:kondo-rule? true} :default nil))) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:clj-kondo {:kondo-rule? true} :default nil))) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:clj-kondo {:kondo-rule? true} :default nil))) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:clj-kondo {:kondo-rule? true} :default nil))) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index fee947cf..788819ab 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -320,7 +320,7 @@ swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] - #?(:bb (assert kondo-rule?) + #?(:clj-kondo (assert kondo-rule?) :default nil) (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) From c783589dbbe1418033a4a49a5b2c897aa334180f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 15:27:31 -0500 Subject: [PATCH 20/39] wip --- .../compojure-api/compojure/api/common.clj | 4 +-- .../compojure-api/compojure/api/core.clj | 31 +++++++++---------- .../compojure-api/compojure/api/meta.clj | 24 +++++++------- .../compojure-api/compojure/api/meta.clj | 22 +++++++------ src/compojure/api/meta.cljc | 22 +++++++------ 5 files changed, 54 insertions(+), 49 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj index 23951dd5..78a21197 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj @@ -1,5 +1,5 @@ (ns compojure.api.common - #?@(:bb [] + #?@(:default [] :default [(:require [linked.core :as linked])])) (defn plain-map? @@ -52,7 +52,7 @@ x y)) -#?(:bb nil +#?(:default nil :default (defn fifo-memoize [f size] "Returns a memoized version of a referentially transparent f. The diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj index 74db7e9e..5ecbacff 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj @@ -1,11 +1,10 @@ -;; :bb reader feature assumes clj-kondo (ns compojure.api.core (:require [compojure.api.meta :as meta] - #?@(:bb [] + #?@(:default [] :default [[compojure.api.async] [compojure.core :as compojure]]) - [compojure.api.routes #?(:bb :as-alias :default :as) routes] - [compojure.api.middleware #?(:bb :as-alias :default :as) mw])) + [compojure.api.routes #?(:default :as-alias :default :as) routes] + [compojure.api.middleware #?(:default :as-alias :default :as) mw])) (defn ring-handler "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" @@ -17,7 +16,7 @@ (defn routes "Create a Ring handler by combining several handlers into one." [& handlers] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:default (throw (ex-info "Not supported in bb")) :default (let [handlers (seq (keep identity (flatten handlers)))] (routes/map->Route {:childs (vec handlers) @@ -43,7 +42,7 @@ "Routes without route-documentation. Can be used to wrap routes, not satisfying compojure.api.routes/Routing -protocol." [& handlers] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:default (throw (ex-info "Not supported in bb")) :default (let [handlers (keep identity handlers)] (routes/map->Route {:handler (meta/routing handlers)})))) @@ -70,7 +69,7 @@ {:style/indent 1 :supercedes "middleware"} [middleware & body] - #?(:bb (throw (ex-info "Not supported in bb")) + #?(:default (throw (ex-info "Not supported in bb")) :default (let [handler (apply routes body) x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] @@ -79,13 +78,13 @@ {:childs [handler] :handler x-handler})))) -(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:bb true :default false)})) +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:default true :default false)})) -(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:bb {:kondo-rule? true} :default nil))) -(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:bb {:kondo-rule? true} :default nil))) +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:default {:kondo-rule? true} :default nil))) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:default {:kondo-rule? true} :default nil))) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:default {:kondo-rule? true} :default nil))) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:default {:kondo-rule? true} :default nil))) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:default {:kondo-rule? true} :default nil))) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:default {:kondo-rule? true} :default nil))) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:default {:kondo-rule? true} :default nil))) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:default {:kondo-rule? true} :default nil))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index fee947cf..c586cfe6 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -1,14 +1,15 @@ (ns compojure.api.meta - (:require [compojure.api.common :as common :refer [extract-parameters]] - [compojure.api.middleware :as mw] - [compojure.api.routes :as routes] - [plumbing.core :as p] - [plumbing.fnk.impl :as fnk-impl] - [ring.swagger.common :as rsc] - [ring.swagger.json-schema :as js] + (:require [clojure.walk :as walk] + [compojure.api.common :as common :refer [extract-parameters]] + [compojure.api.middleware #?(:default :as-alias :default :as) mw] + [compojure.api.routes #?(:default :as-alias :default :as) routes] + [plumbing.core #?(:default :as-alias :default :as) p] + [plumbing.fnk.impl #?(:default :as-alias :default :as) fnk-impl] + [ring.swagger.common #?(:default :as-alias :default :as) rsc] + [ring.swagger.json-schema #?(:default :as-alias :default :as) js] [schema.core :as s] [schema-tools.core :as st] - [compojure.api.coerce :as coerce] + [compojure.api.coerce #?(:default :as-alias :default :as) coerce] compojure.core)) (def +compojure-api-request+ @@ -55,6 +56,7 @@ (defn fnk-schema [bind] (->> (:input-schema + ;;TODO clj-kondo (fnk-impl/letk-input-schema-and-body-form nil (with-meta bind {:schema s/Any}) [] nil)) reverse @@ -272,12 +274,12 @@ (fn [cur-body-form [bind-form]] (if (symbol? bind-form) `(let [~bind-form nil] ~cur-body-form) - (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form + (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form ;;TODO clj-kondo &env (fnk-impl/ensure-schema-metadata &env bind-form) [] cur-body-form) - body-form (clojure.walk/prewalk-replace {'plumbing.fnk.schema/safe-get 'clojure.core/get} body-form)] + body-form (walk/prewalk-replace {'plumbing.fnk.schema/safe-get 'clojure.core/get} body-form)] `(let [~map-sym nil] ~body-form)))) `(do ~@body) (reverse (partition 2 bindings)))) @@ -320,7 +322,7 @@ swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] - #?(:bb (assert kondo-rule?) + #?(:default (assert kondo-rule?) :default nil) (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index ba00d78f..c586cfe6 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -1,14 +1,15 @@ (ns compojure.api.meta - (:require [compojure.api.common :as common :refer [extract-parameters]] - [compojure.api.middleware :as mw] - [compojure.api.routes :as routes] - [plumbing.core :as p] - [plumbing.fnk.impl :as fnk-impl] - [ring.swagger.common :as rsc] - [ring.swagger.json-schema :as js] + (:require [clojure.walk :as walk] + [compojure.api.common :as common :refer [extract-parameters]] + [compojure.api.middleware #?(:default :as-alias :default :as) mw] + [compojure.api.routes #?(:default :as-alias :default :as) routes] + [plumbing.core #?(:default :as-alias :default :as) p] + [plumbing.fnk.impl #?(:default :as-alias :default :as) fnk-impl] + [ring.swagger.common #?(:default :as-alias :default :as) rsc] + [ring.swagger.json-schema #?(:default :as-alias :default :as) js] [schema.core :as s] [schema-tools.core :as st] - [compojure.api.coerce :as coerce] + [compojure.api.coerce #?(:default :as-alias :default :as) coerce] compojure.core)) (def +compojure-api-request+ @@ -55,6 +56,7 @@ (defn fnk-schema [bind] (->> (:input-schema + ;;TODO clj-kondo (fnk-impl/letk-input-schema-and-body-form nil (with-meta bind {:schema s/Any}) [] nil)) reverse @@ -272,12 +274,12 @@ (fn [cur-body-form [bind-form]] (if (symbol? bind-form) `(let [~bind-form nil] ~cur-body-form) - (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form + (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form ;;TODO clj-kondo &env (fnk-impl/ensure-schema-metadata &env bind-form) [] cur-body-form) - body-form (clojure.walk/prewalk-replace {'plumbing.fnk.schema/safe-get 'clojure.core/get} body-form)] + body-form (walk/prewalk-replace {'plumbing.fnk.schema/safe-get 'clojure.core/get} body-form)] `(let [~map-sym nil] ~body-form)))) `(do ~@body) (reverse (partition 2 bindings)))) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 788819ab..d4787783 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -1,14 +1,15 @@ (ns compojure.api.meta - (:require [compojure.api.common :as common :refer [extract-parameters]] - [compojure.api.middleware :as mw] - [compojure.api.routes :as routes] - [plumbing.core :as p] - [plumbing.fnk.impl :as fnk-impl] - [ring.swagger.common :as rsc] - [ring.swagger.json-schema :as js] + (:require [clojure.walk :as walk] + [compojure.api.common :as common :refer [extract-parameters]] + [compojure.api.middleware #?(:clj-kondo :as-alias :default :as) mw] + [compojure.api.routes #?(:clj-kondo :as-alias :default :as) routes] + [plumbing.core #?(:clj-kondo :as-alias :default :as) p] + [plumbing.fnk.impl #?(:clj-kondo :as-alias :default :as) fnk-impl] + [ring.swagger.common #?(:clj-kondo :as-alias :default :as) rsc] + [ring.swagger.json-schema #?(:clj-kondo :as-alias :default :as) js] [schema.core :as s] [schema-tools.core :as st] - [compojure.api.coerce :as coerce] + [compojure.api.coerce #?(:clj-kondo :as-alias :default :as) coerce] compojure.core)) (def +compojure-api-request+ @@ -55,6 +56,7 @@ (defn fnk-schema [bind] (->> (:input-schema + ;;TODO clj-kondo (fnk-impl/letk-input-schema-and-body-form nil (with-meta bind {:schema s/Any}) [] nil)) reverse @@ -272,12 +274,12 @@ (fn [cur-body-form [bind-form]] (if (symbol? bind-form) `(let [~bind-form nil] ~cur-body-form) - (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form + (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form ;;TODO clj-kondo &env (fnk-impl/ensure-schema-metadata &env bind-form) [] cur-body-form) - body-form (clojure.walk/prewalk-replace {'plumbing.fnk.schema/safe-get 'clojure.core/get} body-form)] + body-form (walk/prewalk-replace {'plumbing.fnk.schema/safe-get 'clojure.core/get} body-form)] `(let [~map-sym nil] ~body-form)))) `(do ~@body) (reverse (partition 2 bindings)))) From 595d41e7cee6425eba1672cb39e70624092666af Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 15:36:13 -0500 Subject: [PATCH 21/39] wip --- .../compojure-api/compojure/api/meta.clj | 30 +++++++++++-------- .../compojure-api/compojure/api/meta.clj | 30 +++++++++++-------- src/compojure/api/meta.cljc | 30 +++++++++++-------- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index c586cfe6..0a3b7a80 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -7,10 +7,11 @@ [plumbing.fnk.impl #?(:default :as-alias :default :as) fnk-impl] [ring.swagger.common #?(:default :as-alias :default :as) rsc] [ring.swagger.json-schema #?(:default :as-alias :default :as) js] - [schema.core :as s] - [schema-tools.core :as st] + #?@(:default [] + :default [[schema.core :as s] + [schema-tools.core :as st]]) [compojure.api.coerce #?(:default :as-alias :default :as) coerce] - compojure.core)) + [compojure.core #?(:default :as-alias :default :as) comp-core])) (def +compojure-api-request+ "lexically bound ring-request for handlers." @@ -62,11 +63,12 @@ reverse (into {}))) -(s/defn src-coerce! +(defn src-coerce! "Return source code for coerce! for a schema with coercion type, extracted from a key in a ring request." - [schema, key, type :- mw/CoercionType] + [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) + (assert (#{:body :body :string :response} type)) `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) (defn- convert-return [schema] @@ -209,7 +211,8 @@ (let [schema (strict (fnk-schema form-params))] (-> acc (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) - (update-in [:swagger :parameters :formData] st/merge schema) + #?@(:default [] + :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) ; restructures multipart-params with plumbing letk notation and consumes "multipart/form-data" @@ -218,7 +221,8 @@ (let [schema (strict (fnk-schema params))] (-> acc (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) - (update-in [:swagger :parameters :formData] st/merge schema) + #?@(:default [] + :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["multipart/form-data"])))) ; restructures header-params with plumbing letk notation. Example: @@ -286,7 +290,7 @@ (defn routing [handlers] (if-let [handlers (seq (keep identity (flatten handlers)))] - (apply compojure.core/routes handlers) + (apply comp-core/routes handlers) (fn ([_] nil) ([_ respond _] (respond nil))))) ;; @@ -356,17 +360,17 @@ (if context? ;; context - (let [form `(compojure.core/routes ~@body) + (let [form `(comp-core/routes ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) - form `(compojure.core/context ~path ~arg-with-request ~form) + form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes childs (let [form (vec body) form (if (seq letks) `(dummy-letk ~letks ~form) form) form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(compojure.core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) form `(fn [~'+compojure-api-request+] ~form) form `(~form {})] form)] @@ -377,7 +381,7 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form (compojure.core/compile-route method path arg-with-request (list form)) - form (if (seq middleware) `(compojure.core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] + form (comp-core/compile-route method path arg-with-request (list form)) + form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index c586cfe6..0a3b7a80 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -7,10 +7,11 @@ [plumbing.fnk.impl #?(:default :as-alias :default :as) fnk-impl] [ring.swagger.common #?(:default :as-alias :default :as) rsc] [ring.swagger.json-schema #?(:default :as-alias :default :as) js] - [schema.core :as s] - [schema-tools.core :as st] + #?@(:default [] + :default [[schema.core :as s] + [schema-tools.core :as st]]) [compojure.api.coerce #?(:default :as-alias :default :as) coerce] - compojure.core)) + [compojure.core #?(:default :as-alias :default :as) comp-core])) (def +compojure-api-request+ "lexically bound ring-request for handlers." @@ -62,11 +63,12 @@ reverse (into {}))) -(s/defn src-coerce! +(defn src-coerce! "Return source code for coerce! for a schema with coercion type, extracted from a key in a ring request." - [schema, key, type :- mw/CoercionType] + [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) + (assert (#{:body :body :string :response} type)) `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) (defn- convert-return [schema] @@ -209,7 +211,8 @@ (let [schema (strict (fnk-schema form-params))] (-> acc (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) - (update-in [:swagger :parameters :formData] st/merge schema) + #?@(:default [] + :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) ; restructures multipart-params with plumbing letk notation and consumes "multipart/form-data" @@ -218,7 +221,8 @@ (let [schema (strict (fnk-schema params))] (-> acc (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) - (update-in [:swagger :parameters :formData] st/merge schema) + #?@(:default [] + :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["multipart/form-data"])))) ; restructures header-params with plumbing letk notation. Example: @@ -286,7 +290,7 @@ (defn routing [handlers] (if-let [handlers (seq (keep identity (flatten handlers)))] - (apply compojure.core/routes handlers) + (apply comp-core/routes handlers) (fn ([_] nil) ([_ respond _] (respond nil))))) ;; @@ -356,17 +360,17 @@ (if context? ;; context - (let [form `(compojure.core/routes ~@body) + (let [form `(comp-core/routes ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) - form `(compojure.core/context ~path ~arg-with-request ~form) + form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes childs (let [form (vec body) form (if (seq letks) `(dummy-letk ~letks ~form) form) form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(compojure.core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) form `(fn [~'+compojure-api-request+] ~form) form `(~form {})] form)] @@ -377,7 +381,7 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form (compojure.core/compile-route method path arg-with-request (list form)) - form (if (seq middleware) `(compojure.core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] + form (comp-core/compile-route method path arg-with-request (list form)) + form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index d4787783..3da9c979 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -7,10 +7,11 @@ [plumbing.fnk.impl #?(:clj-kondo :as-alias :default :as) fnk-impl] [ring.swagger.common #?(:clj-kondo :as-alias :default :as) rsc] [ring.swagger.json-schema #?(:clj-kondo :as-alias :default :as) js] - [schema.core :as s] - [schema-tools.core :as st] + #?@(:clj-kondo [] + :default [[schema.core :as s] + [schema-tools.core :as st]]) [compojure.api.coerce #?(:clj-kondo :as-alias :default :as) coerce] - compojure.core)) + [compojure.core #?(:clj-kondo :as-alias :default :as) comp-core])) (def +compojure-api-request+ "lexically bound ring-request for handlers." @@ -62,11 +63,12 @@ reverse (into {}))) -(s/defn src-coerce! +(defn src-coerce! "Return source code for coerce! for a schema with coercion type, extracted from a key in a ring request." - [schema, key, type :- mw/CoercionType] + [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) + (assert (#{:body :body :string :response} type)) `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) (defn- convert-return [schema] @@ -209,7 +211,8 @@ (let [schema (strict (fnk-schema form-params))] (-> acc (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) - (update-in [:swagger :parameters :formData] st/merge schema) + #?@(:clj-kondo [] + :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) ; restructures multipart-params with plumbing letk notation and consumes "multipart/form-data" @@ -218,7 +221,8 @@ (let [schema (strict (fnk-schema params))] (-> acc (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) - (update-in [:swagger :parameters :formData] st/merge schema) + #?@(:clj-kondo [] + :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["multipart/form-data"])))) ; restructures header-params with plumbing letk notation. Example: @@ -286,7 +290,7 @@ (defn routing [handlers] (if-let [handlers (seq (keep identity (flatten handlers)))] - (apply compojure.core/routes handlers) + (apply comp-core/routes handlers) (fn ([_] nil) ([_ respond _] (respond nil))))) ;; @@ -356,17 +360,17 @@ (if context? ;; context - (let [form `(compojure.core/routes ~@body) + (let [form `(comp-core/routes ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) form (if (seq middleware) `((mw/compose-middleware ~middleware) ~form) form) - form `(compojure.core/context ~path ~arg-with-request ~form) + form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes childs (let [form (vec body) form (if (seq letks) `(dummy-letk ~letks ~form) form) form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(compojure.core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) form `(fn [~'+compojure-api-request+] ~form) form `(~form {})] form)] @@ -377,7 +381,7 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form (compojure.core/compile-route method path arg-with-request (list form)) - form (if (seq middleware) `(compojure.core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] + form (comp-core/compile-route method path arg-with-request (list form)) + form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) From fea9599a3027a781c69fd6446035d5349b29fb76 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 15:49:09 -0500 Subject: [PATCH 22/39] [skip ci] --- .../compojure-api/compojure/api/meta.clj | 73 +++++++++++-------- .../compojure-api/compojure/api/meta.clj | 73 +++++++++++-------- src/compojure/api/meta.cljc | 73 +++++++++++-------- 3 files changed, 132 insertions(+), 87 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index 0a3b7a80..9944240f 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -13,6 +13,11 @@ [compojure.api.coerce #?(:default :as-alias :default :as) coerce] [compojure.core #?(:default :as-alias :default :as) comp-core])) +(defmacro ^:private system-property-check + [& body] + #?(:default nil + :default `(do ~@body))) + (def +compojure-api-request+ "lexically bound ring-request for handlers." '+compojure-api-request+) @@ -55,25 +60,28 @@ (dissoc schema 'schema.core/Keyword)) (defn fnk-schema [bind] - (->> - (:input-schema - ;;TODO clj-kondo - (fnk-impl/letk-input-schema-and-body-form - nil (with-meta bind {:schema s/Any}) [] nil)) - reverse - (into {}))) + #?(:default {} + :default (->> + (:input-schema + ;;TODO clj-kondo + (fnk-impl/letk-input-schema-and-body-form + nil (with-meta bind {:schema s/Any}) [] nil)) + reverse + (into {})))) (defn src-coerce! "Return source code for coerce! for a schema with coercion type, extracted from a key in a ring request." [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) - (assert (#{:body :body :string :response} type)) + (assert (#{:body :string :response} type)) `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) (defn- convert-return [schema] {200 {:schema schema - :description (or (js/json-schema-meta schema) "")}}) + :description (or #?(:default nil + :default (js/json-schema-meta schema)) + "")}}) ;; ;; Extension point @@ -162,10 +170,11 @@ ; Examples: ; :body [user User] (defmethod restructure-param :body [_ [value schema :as bv] acc] - (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) - (assert (= 2 (count bv)) - (str ":body should be [sym schema], provided: " bv - "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true"))) + (system-property-check + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) + (assert (= 2 (count bv)) + (str ":body should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :body-params :body)]) (assoc-in [:swagger :parameters :body] schema))) @@ -175,10 +184,11 @@ ; Examples: ; :query [user User] (defmethod restructure-param :query [_ [value schema :as bv] acc] - (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) - (assert (= 2 (count bv)) - (str ":query should be [sym schema], provided: " bv - "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true"))) + (system-property-check + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) + (assert (= 2 (count bv)) + (str ":query should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :query-params :string)]) (assoc-in [:swagger :parameters :query] schema))) @@ -188,10 +198,11 @@ ; Examples: ; :headers [headers Headers] (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"))) + (system-property-check + (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")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :headers :string)]) @@ -259,7 +270,10 @@ ; route-specific override for coercers (defmethod restructure-param :coercion [_ coercion acc] - (update-in acc [:middleware] conj [mw/wrap-coercion coercion])) + (update-in acc [:middleware] conj [#?(:default `mw/wrap-coercion + ;;FIXME why not quoted? + :default mw/wrap-coercion) + coercion])) ;; ;; Impl @@ -367,13 +381,14 @@ form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes - childs (let [form (vec body) - form (if (seq letks) `(dummy-letk ~letks ~form) form) - form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) - form `(fn [~'+compojure-api-request+] ~form) - form `(~form {})] - form)] + childs #?(:default nil + :default (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form))] `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index 0a3b7a80..9944240f 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -13,6 +13,11 @@ [compojure.api.coerce #?(:default :as-alias :default :as) coerce] [compojure.core #?(:default :as-alias :default :as) comp-core])) +(defmacro ^:private system-property-check + [& body] + #?(:default nil + :default `(do ~@body))) + (def +compojure-api-request+ "lexically bound ring-request for handlers." '+compojure-api-request+) @@ -55,25 +60,28 @@ (dissoc schema 'schema.core/Keyword)) (defn fnk-schema [bind] - (->> - (:input-schema - ;;TODO clj-kondo - (fnk-impl/letk-input-schema-and-body-form - nil (with-meta bind {:schema s/Any}) [] nil)) - reverse - (into {}))) + #?(:default {} + :default (->> + (:input-schema + ;;TODO clj-kondo + (fnk-impl/letk-input-schema-and-body-form + nil (with-meta bind {:schema s/Any}) [] nil)) + reverse + (into {})))) (defn src-coerce! "Return source code for coerce! for a schema with coercion type, extracted from a key in a ring request." [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) - (assert (#{:body :body :string :response} type)) + (assert (#{:body :string :response} type)) `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) (defn- convert-return [schema] {200 {:schema schema - :description (or (js/json-schema-meta schema) "")}}) + :description (or #?(:default nil + :default (js/json-schema-meta schema)) + "")}}) ;; ;; Extension point @@ -162,10 +170,11 @@ ; Examples: ; :body [user User] (defmethod restructure-param :body [_ [value schema :as bv] acc] - (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) - (assert (= 2 (count bv)) - (str ":body should be [sym schema], provided: " bv - "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true"))) + (system-property-check + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) + (assert (= 2 (count bv)) + (str ":body should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :body-params :body)]) (assoc-in [:swagger :parameters :body] schema))) @@ -175,10 +184,11 @@ ; Examples: ; :query [user User] (defmethod restructure-param :query [_ [value schema :as bv] acc] - (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) - (assert (= 2 (count bv)) - (str ":query should be [sym schema], provided: " bv - "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true"))) + (system-property-check + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) + (assert (= 2 (count bv)) + (str ":query should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :query-params :string)]) (assoc-in [:swagger :parameters :query] schema))) @@ -188,10 +198,11 @@ ; Examples: ; :headers [headers Headers] (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"))) + (system-property-check + (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")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :headers :string)]) @@ -259,7 +270,10 @@ ; route-specific override for coercers (defmethod restructure-param :coercion [_ coercion acc] - (update-in acc [:middleware] conj [mw/wrap-coercion coercion])) + (update-in acc [:middleware] conj [#?(:default `mw/wrap-coercion + ;;FIXME why not quoted? + :default mw/wrap-coercion) + coercion])) ;; ;; Impl @@ -367,13 +381,14 @@ form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes - childs (let [form (vec body) - form (if (seq letks) `(dummy-letk ~letks ~form) form) - form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) - form `(fn [~'+compojure-api-request+] ~form) - form `(~form {})] - form)] + childs #?(:default nil + :default (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form))] `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 3da9c979..98ff5c9f 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -13,6 +13,11 @@ [compojure.api.coerce #?(:clj-kondo :as-alias :default :as) coerce] [compojure.core #?(:clj-kondo :as-alias :default :as) comp-core])) +(defmacro ^:private system-property-check + [& body] + #?(:clj-kondo nil + :default `(do ~@body))) + (def +compojure-api-request+ "lexically bound ring-request for handlers." '+compojure-api-request+) @@ -55,25 +60,28 @@ (dissoc schema 'schema.core/Keyword)) (defn fnk-schema [bind] - (->> - (:input-schema - ;;TODO clj-kondo - (fnk-impl/letk-input-schema-and-body-form - nil (with-meta bind {:schema s/Any}) [] nil)) - reverse - (into {}))) + #?(:clj-kondo {} + :default (->> + (:input-schema + ;;TODO clj-kondo + (fnk-impl/letk-input-schema-and-body-form + nil (with-meta bind {:schema s/Any}) [] nil)) + reverse + (into {})))) (defn src-coerce! "Return source code for coerce! for a schema with coercion type, extracted from a key in a ring request." [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) - (assert (#{:body :body :string :response} type)) + (assert (#{:body :string :response} type)) `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) (defn- convert-return [schema] {200 {:schema schema - :description (or (js/json-schema-meta schema) "")}}) + :description (or #?(:clj-kondo nil + :default (js/json-schema-meta schema)) + "")}}) ;; ;; Extension point @@ -162,10 +170,11 @@ ; Examples: ; :body [user User] (defmethod restructure-param :body [_ [value schema :as bv] acc] - (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) - (assert (= 2 (count bv)) - (str ":body should be [sym schema], provided: " bv - "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true"))) + (system-property-check + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-body")) + (assert (= 2 (count bv)) + (str ":body should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-body=true")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :body-params :body)]) (assoc-in [:swagger :parameters :body] schema))) @@ -175,10 +184,11 @@ ; Examples: ; :query [user User] (defmethod restructure-param :query [_ [value schema :as bv] acc] - (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) - (assert (= 2 (count bv)) - (str ":query should be [sym schema], provided: " bv - "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true"))) + (system-property-check + (when-not (= "true" (System/getProperty "compojure.api.meta.allow-bad-query")) + (assert (= 2 (count bv)) + (str ":query should be [sym schema], provided: " bv + "\nDisable this check with -Dcompojure.api.meta.allow-bad-query=true")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :query-params :string)]) (assoc-in [:swagger :parameters :query] schema))) @@ -188,10 +198,11 @@ ; Examples: ; :headers [headers Headers] (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"))) + (system-property-check + (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")))) (-> acc (update-in [:lets] into [value (src-coerce! schema :headers :string)]) @@ -259,7 +270,10 @@ ; route-specific override for coercers (defmethod restructure-param :coercion [_ coercion acc] - (update-in acc [:middleware] conj [mw/wrap-coercion coercion])) + (update-in acc [:middleware] conj [#?(:clj-kondo `mw/wrap-coercion + ;;FIXME why not quoted? + :default mw/wrap-coercion) + coercion])) ;; ;; Impl @@ -367,13 +381,14 @@ form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes - childs (let [form (vec body) - form (if (seq letks) `(dummy-letk ~letks ~form) form) - form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) - form `(fn [~'+compojure-api-request+] ~form) - form `(~form {})] - form)] + childs #?(:clj-kondo nil + :default (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form))] `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) From 9918ede96ea7162c500f219fab5c8705b4e37f01 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 15:51:40 -0500 Subject: [PATCH 23/39] [skip ci] --- .../metosin/compojure-api/compojure/api/meta.clj | 15 ++++++++++++--- .../metosin/compojure-api/compojure/api/meta.clj | 15 ++++++++++++--- src/compojure/api/meta.cljc | 15 ++++++++++++--- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index 9944240f..f4c48821 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -63,7 +63,6 @@ #?(:default {} :default (->> (:input-schema - ;;TODO clj-kondo (fnk-impl/letk-input-schema-and-body-form nil (with-meta bind {:schema s/Any}) [] nil)) reverse @@ -279,12 +278,17 @@ ;; Impl ;; +#?(:default nil + :default (defmacro dummy-let "Dummy let-macro used in resolving route-docs. not part of normal invocation chain." [bindings & body] (let [bind-form (vec (apply concat (for [n (take-nth 2 bindings)] [n nil])))] `(let ~bind-form ~@body))) +) +#?(:default nil + :default (defmacro dummy-letk "Dummy letk-macro used in resolving route-docs. not part of normal invocation chain." [bindings & body] @@ -301,11 +305,15 @@ `(let [~map-sym nil] ~body-form)))) `(do ~@body) (reverse (partition 2 bindings)))) +) +#?(:default nil + :default (defn routing [handlers] (if-let [handlers (seq (keep identity (flatten handlers)))] (apply comp-core/routes handlers) (fn ([_] nil) ([_ respond _] (respond nil))))) +) ;; ;; Api @@ -329,8 +337,9 @@ ;; GET "/route" req (symbol? arg) [path-string [+compojure-api-request+ arg] arg arg] :else (throw - (RuntimeException. - (str "unknown compojure destruction syntax: " arg)))))) + (ex-info + (str "unknown compojure destruction syntax: " arg) + {}))))) (defn merge-parameters "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index 9944240f..f4c48821 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -63,7 +63,6 @@ #?(:default {} :default (->> (:input-schema - ;;TODO clj-kondo (fnk-impl/letk-input-schema-and-body-form nil (with-meta bind {:schema s/Any}) [] nil)) reverse @@ -279,12 +278,17 @@ ;; Impl ;; +#?(:default nil + :default (defmacro dummy-let "Dummy let-macro used in resolving route-docs. not part of normal invocation chain." [bindings & body] (let [bind-form (vec (apply concat (for [n (take-nth 2 bindings)] [n nil])))] `(let ~bind-form ~@body))) +) +#?(:default nil + :default (defmacro dummy-letk "Dummy letk-macro used in resolving route-docs. not part of normal invocation chain." [bindings & body] @@ -301,11 +305,15 @@ `(let [~map-sym nil] ~body-form)))) `(do ~@body) (reverse (partition 2 bindings)))) +) +#?(:default nil + :default (defn routing [handlers] (if-let [handlers (seq (keep identity (flatten handlers)))] (apply comp-core/routes handlers) (fn ([_] nil) ([_ respond _] (respond nil))))) +) ;; ;; Api @@ -329,8 +337,9 @@ ;; GET "/route" req (symbol? arg) [path-string [+compojure-api-request+ arg] arg arg] :else (throw - (RuntimeException. - (str "unknown compojure destruction syntax: " arg)))))) + (ex-info + (str "unknown compojure destruction syntax: " arg) + {}))))) (defn merge-parameters "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 98ff5c9f..578b7caa 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -63,7 +63,6 @@ #?(:clj-kondo {} :default (->> (:input-schema - ;;TODO clj-kondo (fnk-impl/letk-input-schema-and-body-form nil (with-meta bind {:schema s/Any}) [] nil)) reverse @@ -279,12 +278,17 @@ ;; Impl ;; +#?(:clj-kondo nil + :default (defmacro dummy-let "Dummy let-macro used in resolving route-docs. not part of normal invocation chain." [bindings & body] (let [bind-form (vec (apply concat (for [n (take-nth 2 bindings)] [n nil])))] `(let ~bind-form ~@body))) +) +#?(:clj-kondo nil + :default (defmacro dummy-letk "Dummy letk-macro used in resolving route-docs. not part of normal invocation chain." [bindings & body] @@ -301,11 +305,15 @@ `(let [~map-sym nil] ~body-form)))) `(do ~@body) (reverse (partition 2 bindings)))) +) +#?(:clj-kondo nil + :default (defn routing [handlers] (if-let [handlers (seq (keep identity (flatten handlers)))] (apply comp-core/routes handlers) (fn ([_] nil) ([_ respond _] (respond nil))))) +) ;; ;; Api @@ -329,8 +337,9 @@ ;; GET "/route" req (symbol? arg) [path-string [+compojure-api-request+ arg] arg arg] :else (throw - (RuntimeException. - (str "unknown compojure destruction syntax: " arg)))))) + (ex-info + (str "unknown compojure destruction syntax: " arg) + {}))))) (defn merge-parameters "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." From 24ba4f40bb0ec32b6efb21a492e8e5b419ed7018 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 15:55:56 -0500 Subject: [PATCH 24/39] wip --- .../metosin/compojure-api/compojure/api/core.clj | 9 +++++---- .../metosin/compojure-api/compojure/api/meta.clj | 10 ++++++---- .../metosin/compojure-api/compojure/api/core.clj | 9 +++++---- .../metosin/compojure-api/compojure/api/meta.clj | 10 ++++++---- src/compojure/api/core.cljc | 9 +++++---- src/compojure/api/meta.cljc | 10 ++++++---- 6 files changed, 33 insertions(+), 24 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj index 5ecbacff..772a0ed2 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj @@ -56,10 +56,11 @@ :deprecated "1.1.14" :superseded-by "route-middleware"} [middleware & body] - (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) - (println (str "compojure.api.core.middleware is deprecated because of security issues. " - "Please use route-middleware instead. middleware will be disabled in a future release." - "Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning."))) + #?(:default nil + :default (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) + (println (str "compojure.api.core.middleware is deprecated because of security issues. " + "Please use route-middleware instead. middleware will be disabled in a future release." + "Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning.")))) `(let [body# (routes ~@body) wrap-mw# (mw/compose-middleware ~middleware)] (routes/create nil nil {} [body#] (wrap-mw# body#)))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index f4c48821..7e089272 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -344,9 +344,10 @@ (defn merge-parameters "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." [{:keys [responses swagger] :as parameters}] - (cond-> parameters - (seq responses) (assoc :responses (common/merge-vector responses)) - swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) + #?(:default parameters + :default (cond-> parameters + (seq responses) (assoc :responses (common/merge-vector responses)) + swagger (-> (dissoc :swagger) (rsc/deep-merge swagger))))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] #?(:default (assert kondo-rule?) @@ -405,7 +406,8 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form (comp-core/compile-route method path arg-with-request (list form)) + form #?(:default `(comp-core/let-request [~arg-with-request {}] ~form) + :default (comp-core/compile-route method path arg-with-request (list form))) form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj index 5ecbacff..772a0ed2 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj @@ -56,10 +56,11 @@ :deprecated "1.1.14" :superseded-by "route-middleware"} [middleware & body] - (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) - (println (str "compojure.api.core.middleware is deprecated because of security issues. " - "Please use route-middleware instead. middleware will be disabled in a future release." - "Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning."))) + #?(:default nil + :default (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) + (println (str "compojure.api.core.middleware is deprecated because of security issues. " + "Please use route-middleware instead. middleware will be disabled in a future release." + "Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning.")))) `(let [body# (routes ~@body) wrap-mw# (mw/compose-middleware ~middleware)] (routes/create nil nil {} [body#] (wrap-mw# body#)))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index f4c48821..7e089272 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -344,9 +344,10 @@ (defn merge-parameters "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." [{:keys [responses swagger] :as parameters}] - (cond-> parameters - (seq responses) (assoc :responses (common/merge-vector responses)) - swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) + #?(:default parameters + :default (cond-> parameters + (seq responses) (assoc :responses (common/merge-vector responses)) + swagger (-> (dissoc :swagger) (rsc/deep-merge swagger))))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] #?(:default (assert kondo-rule?) @@ -405,7 +406,8 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form (comp-core/compile-route method path arg-with-request (list form)) + form #?(:default `(comp-core/let-request [~arg-with-request {}] ~form) + :default (comp-core/compile-route method path arg-with-request (list form))) form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) diff --git a/src/compojure/api/core.cljc b/src/compojure/api/core.cljc index 2337bd36..6cfcc324 100644 --- a/src/compojure/api/core.cljc +++ b/src/compojure/api/core.cljc @@ -56,10 +56,11 @@ :deprecated "1.1.14" :superseded-by "route-middleware"} [middleware & body] - (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) - (println (str "compojure.api.core.middleware is deprecated because of security issues. " - "Please use route-middleware instead. middleware will be disabled in a future release." - "Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning."))) + #?(:clj-kondo nil + :default (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) + (println (str "compojure.api.core.middleware is deprecated because of security issues. " + "Please use route-middleware instead. middleware will be disabled in a future release." + "Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning.")))) `(let [body# (routes ~@body) wrap-mw# (mw/compose-middleware ~middleware)] (routes/create nil nil {} [body#] (wrap-mw# body#)))) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 578b7caa..04914120 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -344,9 +344,10 @@ (defn merge-parameters "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." [{:keys [responses swagger] :as parameters}] - (cond-> parameters - (seq responses) (assoc :responses (common/merge-vector responses)) - swagger (-> (dissoc :swagger) (rsc/deep-merge swagger)))) + #?(:clj-kondo parameters + :default (cond-> parameters + (seq responses) (assoc :responses (common/merge-vector responses)) + swagger (-> (dissoc :swagger) (rsc/deep-merge swagger))))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] #?(:clj-kondo (assert kondo-rule?) @@ -405,7 +406,8 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form (comp-core/compile-route method path arg-with-request (list form)) + form #?(:clj-kondo `(comp-core/let-request [~arg-with-request {}] ~form) + :default (comp-core/compile-route method path arg-with-request (list form))) form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) From 7c7bba986d14d4cb0ac478b0c664bf53d38a44ee Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 16:20:26 -0500 Subject: [PATCH 25/39] wip --- .../metosin/compojure-api/compojure/api/meta.clj | 11 +++++++---- .../imports/metosin/compojure-api/config.edn | 2 +- .../metosin/compojure-api/compojure/api/meta.clj | 11 +++++++---- .../metosin/compojure-api/config.edn | 2 +- scripts/regen_kondo_config.clj | 3 ++- src/compojure/api/meta.cljc | 11 +++++++---- 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index 7e089272..8075bac4 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -381,8 +381,12 @@ ;; response coercion middleware, why not just code? middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] + #?(:default (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form)] + `(fn [~+compojure-api-request+] ~form)) + :default (if context? - ;; context (let [form `(comp-core/routes ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) @@ -406,8 +410,7 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form #?(:default `(comp-core/let-request [~arg-with-request {}] ~form) - :default (comp-core/compile-route method path arg-with-request (list form))) + form (comp-core/compile-route method path arg-with-request (list form)) form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] - `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) + `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form)))))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn index df66b977..2a6b3d4b 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn @@ -1 +1 @@ -{:hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}} \ No newline at end of file +{:linters {:unresolved-namespace {:exclude [(compojure.api.routes)]}}, :hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}} \ No newline at end of file diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index 7e089272..8075bac4 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -381,8 +381,12 @@ ;; response coercion middleware, why not just code? middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] + #?(:default (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form)] + `(fn [~+compojure-api-request+] ~form)) + :default (if context? - ;; context (let [form `(comp-core/routes ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) @@ -406,8 +410,7 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form #?(:default `(comp-core/let-request [~arg-with-request {}] ~form) - :default (comp-core/compile-route method path arg-with-request (list form))) + form (comp-core/compile-route method path arg-with-request (list form)) form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] - `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) + `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form)))))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/config.edn b/resources/clj-kondo.exports/metosin/compojure-api/config.edn index df66b977..2a6b3d4b 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/config.edn +++ b/resources/clj-kondo.exports/metosin/compojure-api/config.edn @@ -1 +1 @@ -{:hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}} \ No newline at end of file +{:linters {:unresolved-namespace {:exclude [(compojure.api.routes)]}}, :hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}} \ No newline at end of file diff --git a/scripts/regen_kondo_config.clj b/scripts/regen_kondo_config.clj index 0e0a7c9f..7a129694 100755 --- a/scripts/regen_kondo_config.clj +++ b/scripts/regen_kondo_config.clj @@ -15,7 +15,8 @@ (spit to (str/replace (slurp from) ":clj-kondo" ":default"))) (spit "resources/clj-kondo.exports/metosin/compojure-api/config.edn" - '{:hooks + '{:linters {:unresolved-namespace {:exclude [(compojure.api.routes)]}} + :hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}})) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 04914120..8ae326d3 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -381,8 +381,12 @@ ;; response coercion middleware, why not just code? middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] + #?(:clj-kondo (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form)] + `(fn [~+compojure-api-request+] ~form)) + :default (if context? - ;; context (let [form `(comp-core/routes ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) @@ -406,8 +410,7 @@ (let [form `(do ~@body) form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) - form #?(:clj-kondo `(comp-core/let-request [~arg-with-request {}] ~form) - :default (comp-core/compile-route method path arg-with-request (list form))) + form (comp-core/compile-route method path arg-with-request (list form)) form (if (seq middleware) `(comp-core/wrap-routes ~form (mw/compose-middleware ~middleware)) form)] - `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form))))) + `(routes/create ~path-string ~method (merge-parameters ~swagger) nil ~form)))))) From 5b5fffa3e27eb9193108d0c4fe704e2e02312e8d Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 16:44:08 -0500 Subject: [PATCH 26/39] init --- .../compojure/api.clj | 353 ++++++++++++++++++ .../compojure-api/compojure/api/meta.clj | 48 ++- .../compojure_api_example/clj_kondo_hooks.clj | 3 +- .../compojure-api/compojure/api/meta.clj | 48 ++- src/compojure/api/meta.cljc | 48 ++- 5 files changed, 448 insertions(+), 52 deletions(-) create mode 100644 dev/compojure_api_kondo_hooks/compojure/api.clj diff --git a/dev/compojure_api_kondo_hooks/compojure/api.clj b/dev/compojure_api_kondo_hooks/compojure/api.clj new file mode 100644 index 00000000..245cf85a --- /dev/null +++ b/dev/compojure_api_kondo_hooks/compojure/api.clj @@ -0,0 +1,353 @@ +;; Copyright © 2024 James Reeves +;; +;; Distributed under the Eclipse Public License, the same as Clojure. +(ns compojure-api-kondo-hooks.compojure.core + (:require [compojure.response :as response] + [clojure.tools.macro :as macro] + [clout.core :as clout] + [ring.middleware.head :refer [wrap-head]] + [ring.util.codec :as codec] + [medley.core :refer [map-vals]])) + +(defn- form-method [request] + (or (get-in request [:form-params "_method"]) + (get-in request [:multipart-params "_method"]))) + +(defn- method-matches? [request method] + (or (nil? method) + (let [request-method (:request-method request)] + (case request-method + :head + (or (= method :head) (= method :get)) + :post + (if-let [fm (form-method request)] + (.equalsIgnoreCase (name method) fm) + (= method :post)) + ;else + (= request-method method))))) + +(defn- head-response [response request method] + (if (and response (= :get method) (= :head (:request-method request))) + (assoc response :body nil) + response)) + +(defn- decode-route-params [params] + (map-vals codec/url-decode params)) + +(defn- assoc-route-params [request params] + (merge-with merge request {:route-params params, :params params})) + +(defn- route-matches [route request] + (let [path (:compojure/path request)] + (clout/route-matches route (cond-> request path (assoc :path-info path))))) + +(defn- route-request [request route] + (if-let [params (route-matches route request)] + (assoc-route-params request (decode-route-params params)))) + +(defn- literal? [x] + (if (coll? x) + (every? literal? x) + (not (or (symbol? x) (list? x))))) + +(defn- prepare-route [route] + (cond + (string? route) + (clout/route-compile route) + (and (vector? route) (literal? route)) + (clout/route-compile + (first route) + (apply hash-map (rest route))) + (vector? route) + `(clout/route-compile + ~(first route) + ~(apply hash-map (rest route))) + :else + `(if (string? ~route) + (clout/route-compile ~route) + ~route))) + +(defn- and-binding [req binds] + `(dissoc (:params ~req) ~@(map keyword (keys binds)) ~@(map str (keys binds)))) + +(defn- symbol-binding [req sym] + `(get-in ~req [:params ~(keyword sym)] (get-in ~req [:params ~(str sym)]))) + +(defn- application-binding [req sym func] + `(~func ~(symbol-binding req sym))) + +(defn- vector-bindings [args req] + (loop [args args, binds {}] + (if (seq args) + (let [[x y z] args] + (cond + (= '& x) + (recur (nnext args) (assoc binds y (and-binding req binds))) + (= :as x) + (recur (nnext args) (assoc binds y req)) + (and (symbol? x) (= :<< y) (nnext args)) + (recur (drop 3 args) (assoc binds x (application-binding req x z))) + (symbol? x) + (recur (next args) (assoc binds x (symbol-binding req x))) + :else + (throw (Exception. (str "Unexpected binding: " x))))) + (mapcat identity binds)))) + +(defn- warn-on-*-bindings! [bindings] + (when (and (vector? bindings) (contains? (set bindings) '*)) + (binding [*out* *err*] + (println "WARNING: * should not be used as a route binding.")))) + +(defn- application-symbols [args] + (loop [args args, syms '()] + (if (seq args) + (let [[x y] args] + (if (and (symbol? x) (= :<< y)) + (recur (drop 3 args) (conj syms x)) + (recur (next args) syms))) + (seq syms)))) + +(defmacro ^:no-doc let-request [[bindings request] & body] + (warn-on-*-bindings! bindings) + (if (vector? bindings) + `(let [~@(vector-bindings bindings request)] + ~(if-let [syms (application-symbols bindings)] + `(if (and ~@(for [s syms] `(not (nil? ~s)))) (do ~@body)) + `(do ~@body))) + `(let [~bindings ~request] ~@body))) + +(defn- wrap-route-middleware [handler] + (fn + ([request] + (if-let [mw (:route-middleware request)] + ((mw handler) request) + (handler request))) + ([request respond raise] + (if-let [mw (:route-middleware request)] + ((mw handler) request respond raise) + (handler request respond raise))))) + +(defn- wrap-route-info [handler route-info] + (fn + ([request] + (handler (assoc request :compojure/route route-info))) + ([request respond raise] + (handler (assoc request :compojure/route route-info) respond raise)))) + +(defn- wrap-route-matches [handler method path] + (fn + ([request] + (if (method-matches? request method) + (if-let [request (route-request request path)] + (-> (handler request) + (head-response request method))))) + ([request respond raise] + (if (method-matches? request method) + (if-let [request (route-request request path)] + (handler request #(respond (head-response % request method)) raise) + (respond nil)) + (respond nil))))) + +(defn- wrap-response [handler] + (fn + ([request] + (response/render (handler request) request)) + ([request respond raise] + (response/send (handler request) request respond raise)))) + +(defn make-route + "Returns a function that will only call the handler if the method and path + match the request." + [method path handler] + (-> handler + (wrap-response) + (wrap-route-middleware) + (wrap-route-info [(or method :any) (str path)]) + (wrap-route-matches method path))) + +(defn compile-route + "Compile a route in the form `(method path bindings & body)` into a function. + Used to create custom route macros." + [method path bindings body] + `(make-route + ~method + ~(prepare-route path) + (fn [request#] + (let-request [~bindings request#] ~@body)))) + +(defn routing + "Apply a list of routes to a Ring request map." + [request & handlers] + (some #(% request) handlers)) + +(defn routes + "Create a Ring handler by combining several handlers into one." + [& handlers] + (fn + ([request] + (apply routing request handlers)) + ([request respond raise] + (letfn [(f [handlers] + (if (seq handlers) + (let [handler (first handlers) + respond' #(if % (respond %) (f (rest handlers)))] + (handler request respond' raise)) + (respond nil)))] + (f handlers))))) + +(defmacro defroutes + "Define a Ring handler function from a sequence of routes. The name may + optionally be followed by a doc-string and metadata map." + [name & routes] + (let [[name routes] (macro/name-with-attributes name routes)] + `(def ~name (routes ~@routes)))) + +(defmacro GET "Generate a `GET` route." + [path args & body] + (compile-route :get path args body)) + +(defmacro POST "Generate a `POST` route." + [path args & body] + (compile-route :post path args body)) + +(defmacro PUT "Generate a `PUT` route." + [path args & body] + (compile-route :put path args body)) + +(defmacro DELETE "Generate a `DELETE` route." + [path args & body] + (compile-route :delete path args body)) + +(defmacro HEAD "Generate a `HEAD` route." + [path args & body] + (compile-route :head path args body)) + +(defmacro OPTIONS "Generate an `OPTIONS` route." + [path args & body] + (compile-route :options path args body)) + +(defmacro PATCH "Generate a `PATCH` route." + [path args & body] + (compile-route :patch path args body)) + +(defmacro ANY "Generate a route that matches any method." + [path args & body] + (compile-route nil path args body)) + +(defn ^:no-doc make-rfn [handler] + (-> handler + wrap-response + wrap-route-middleware + wrap-head)) + +(defmacro rfn "Generate a route that matches any method and path." + [args & body] + `(make-rfn (fn [request#] (let-request [~args request#] ~@body)))) + +(defn- remove-suffix [path suffix] + (subs path 0 (- (count path) (count suffix)))) + +(defn- context-request [request route route-context] + (if-let [params (clout/route-matches route request)] + (let [uri (:uri request) + path (:path-info request uri) + context (or (:context request) "") + subpath (:__path-info params) + params (dissoc params :__path-info)] + (-> request + (assoc-route-params (decode-route-params params)) + (assoc :path-info (if (= subpath "") "/" subpath) + :context (remove-suffix uri subpath)) + (update :compojure/route-context str route-context))))) + +(defn- context-route [route] + (let [re-context {:__path-info #"|/.*"}] + (cond + (string? route) + (clout/route-compile (str route ":__path-info") re-context) + (and (vector? route) (literal? route)) + (clout/route-compile + (str (first route) ":__path-info") + (merge (apply hash-map (rest route)) re-context)) + (vector? route) + `(clout/route-compile + (str ~(first route) ":__path-info") + ~(merge (apply hash-map (rest route)) re-context)) + :else + `(clout/route-compile (str ~route ":__path-info") ~re-context)))) + +(defn ^:no-doc make-context [route path make-handler] + (letfn [(handler + ([request] + (when-let [context-handler (make-handler request)] + (context-handler request))) + ([request respond raise] + (if-let [context-handler (make-handler request)] + (context-handler request respond raise) + (respond nil))))] + (if (#{":__path-info" "/:__path-info"} (:source route)) + handler + (fn + ([request] + (if-let [request (context-request request route path)] + (handler request))) + ([request respond raise] + (if-let [request (context-request request route path)] + (handler request respond raise) + (respond nil))))))) + +(defmacro context + "Give all routes in the form a common path prefix and set of bindings. + + The following example demonstrates defining two routes with a common + path prefix ('/user/:id') and a common binding ('id'): + + (context \"/user/:id\" [id] + (GET \"/profile\" [] ...) + (GET \"/settings\" [] ...))" + [path args & routes] + `(make-context + ~(context-route path) + ~path + (fn [request#] + (let-request [~args request#] + (routes ~@routes))))) + +(defmacro let-routes + "Takes a vector of bindings and a body of routes. + + Equivalent to: + + (let [...] (routes ...))" + [bindings & body] + `(let ~bindings (routes ~@body))) + +(defn- pre-init [middleware] + (let [proxy (middleware + (fn + ([request] + ((:route-handler request) request)) + ([request respond raise] + ((:route-handler request) request respond raise))))] + (fn [handler] + (let [prep-request #(assoc % :route-handler handler)] + (fn + ([request] + (proxy (prep-request request))) + ([request respond raise] + (proxy (prep-request request) respond raise))))))) + +(defn wrap-routes + "Apply a middleware function to routes after they have been matched." + ([handler middleware] + (let [middleware (pre-init middleware) + prep-request (fn [request] + (let [mw (:route-middleware request identity)] + (assoc request :route-middleware (comp mw middleware))))] + (fn + ([request] + (handler (prep-request request))) + ([request respond raise] + (handler (prep-request request) respond raise))))) + ([handler middleware & args] + (wrap-routes handler #(apply middleware % args)))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index 8075bac4..3d79a53b 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -11,7 +11,8 @@ :default [[schema.core :as s] [schema-tools.core :as st]]) [compojure.api.coerce #?(:default :as-alias :default :as) coerce] - [compojure.core #?(:default :as-alias :default :as) comp-core])) + #?(:default [compojure-api-kondo-hooks.compojure.core :as comp-core] + :default [compojure.core :as comp-core]))) (defmacro ^:private system-property-check [& body] @@ -74,7 +75,8 @@ [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) (assert (#{:body :string :response} type)) - `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) + #?(:default `(do ~schema ~key ~type ~+compojure-api-request+) + :default `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+))) (defn- convert-return [schema] {200 {:schema schema @@ -350,8 +352,6 @@ swagger (-> (dissoc :swagger) (rsc/deep-merge swagger))))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] - #?(:default (assert kondo-rule?) - :default nil) (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) @@ -381,11 +381,26 @@ ;; response coercion middleware, why not just code? middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] - #?(:default (let [form `(do ~@body) - form (if (seq letks) `(p/letk ~letks ~form) form) - form (if (seq lets) `(let ~lets ~form) form)] - `(fn [~+compojure-api-request+] ~form)) - :default + #?(:default (do (assert kondo-rule?) + (if context? + ;; context + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form `(comp-core/context ~path ~arg-with-request ~form)] + form) + + ;; endpoints + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (comp-core/compile-route method path arg-with-request (list form)) + form `(fn [~'+compojure-api-request+] + ~'+compojure-api-request+ ;;always used + ~form)] + (prn "form" form) + form))) + :default ;; JVM (if context? ;; context (let [form `(comp-core/routes ~@body) @@ -395,14 +410,13 @@ form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes - childs #?(:default nil - :default (let [form (vec body) - form (if (seq letks) `(dummy-letk ~letks ~form) form) - form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) - form `(fn [~'+compojure-api-request+] ~form) - form `(~form {})] - form))] + childs (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index 2226e780..fd6cf38a 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -6,4 +6,5 @@ (core/GET "/30" [] (resp/ok {:result 30})) (core/GET "/30" req - (resp/ok {:result (:body req)})) + (do +compojure-api-request+ + (resp/ok {:result (:body req)}))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index 8075bac4..3d79a53b 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -11,7 +11,8 @@ :default [[schema.core :as s] [schema-tools.core :as st]]) [compojure.api.coerce #?(:default :as-alias :default :as) coerce] - [compojure.core #?(:default :as-alias :default :as) comp-core])) + #?(:default [compojure-api-kondo-hooks.compojure.core :as comp-core] + :default [compojure.core :as comp-core]))) (defmacro ^:private system-property-check [& body] @@ -74,7 +75,8 @@ [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) (assert (#{:body :string :response} type)) - `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) + #?(:default `(do ~schema ~key ~type ~+compojure-api-request+) + :default `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+))) (defn- convert-return [schema] {200 {:schema schema @@ -350,8 +352,6 @@ swagger (-> (dissoc :swagger) (rsc/deep-merge swagger))))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] - #?(:default (assert kondo-rule?) - :default nil) (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) @@ -381,11 +381,26 @@ ;; response coercion middleware, why not just code? middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] - #?(:default (let [form `(do ~@body) - form (if (seq letks) `(p/letk ~letks ~form) form) - form (if (seq lets) `(let ~lets ~form) form)] - `(fn [~+compojure-api-request+] ~form)) - :default + #?(:default (do (assert kondo-rule?) + (if context? + ;; context + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form `(comp-core/context ~path ~arg-with-request ~form)] + form) + + ;; endpoints + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (comp-core/compile-route method path arg-with-request (list form)) + form `(fn [~'+compojure-api-request+] + ~'+compojure-api-request+ ;;always used + ~form)] + (prn "form" form) + form))) + :default ;; JVM (if context? ;; context (let [form `(comp-core/routes ~@body) @@ -395,14 +410,13 @@ form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes - childs #?(:default nil - :default (let [form (vec body) - form (if (seq letks) `(dummy-letk ~letks ~form) form) - form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) - form `(fn [~'+compojure-api-request+] ~form) - form `(~form {})] - form))] + childs (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 8ae326d3..bab14e67 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -11,7 +11,8 @@ :default [[schema.core :as s] [schema-tools.core :as st]]) [compojure.api.coerce #?(:clj-kondo :as-alias :default :as) coerce] - [compojure.core #?(:clj-kondo :as-alias :default :as) comp-core])) + #?(:clj-kondo [compojure-api-kondo-hooks.compojure.core :as comp-core] + :default [compojure.core :as comp-core]))) (defmacro ^:private system-property-check [& body] @@ -74,7 +75,8 @@ [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) (assert (#{:body :string :response} type)) - `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) + #?(:clj-kondo `(do ~schema ~key ~type ~+compojure-api-request+) + :default `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+))) (defn- convert-return [schema] {200 {:schema schema @@ -350,8 +352,6 @@ swagger (-> (dissoc :swagger) (rsc/deep-merge swagger))))) (defn restructure [method [path arg & args] {:keys [context? kondo-rule?]}] - #?(:clj-kondo (assert kondo-rule?) - :default nil) (let [[options body] (extract-parameters args true) [path-string lets arg-with-request arg] (destructure-compojure-api-request path arg) @@ -381,11 +381,26 @@ ;; response coercion middleware, why not just code? middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] - #?(:clj-kondo (let [form `(do ~@body) - form (if (seq letks) `(p/letk ~letks ~form) form) - form (if (seq lets) `(let ~lets ~form) form)] - `(fn [~+compojure-api-request+] ~form)) - :default + #?(:clj-kondo (do (assert kondo-rule?) + (if context? + ;; context + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form `(comp-core/context ~path ~arg-with-request ~form)] + form) + + ;; endpoints + (let [form `(do ~@body) + form (if (seq letks) `(p/letk ~letks ~form) form) + form (if (seq lets) `(let ~lets ~form) form) + form (comp-core/compile-route method path arg-with-request (list form)) + form `(fn [~'+compojure-api-request+] + ~'+compojure-api-request+ ;;always used + ~form)] + (prn "form" form) + form))) + :default ;; JVM (if context? ;; context (let [form `(comp-core/routes ~@body) @@ -395,14 +410,13 @@ form `(comp-core/context ~path ~arg-with-request ~form) ;; create and apply a separate lookup-function to find the inner routes - childs #?(:clj-kondo nil - :default (let [form (vec body) - form (if (seq letks) `(dummy-letk ~letks ~form) form) - form (if (seq lets) `(dummy-let ~lets ~form) form) - form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) - form `(fn [~'+compojure-api-request+] ~form) - form `(~form {})] - form))] + childs (let [form (vec body) + form (if (seq letks) `(dummy-letk ~letks ~form) form) + form (if (seq lets) `(dummy-let ~lets ~form) form) + form `(comp-core/let-request [~arg-with-request ~'+compojure-api-request+] ~form) + form `(fn [~'+compojure-api-request+] ~form) + form `(~form {})] + form)] `(routes/create ~path-string ~method (merge-parameters ~swagger) ~childs ~form)) From 8c19506185cda4c7d4f9539957d5f04e63bfb6d4 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 16:46:54 -0500 Subject: [PATCH 27/39] [skip ci] --- .../compojure/{api.clj => core.clj} | 0 .../compojure/core.clj | 353 ++++++++++++++++++ .../compojure/core.clj | 353 ++++++++++++++++++ scripts/regen-kondo.clj | 1 + scripts/regen_kondo_config.clj | 6 +- 5 files changed, 711 insertions(+), 2 deletions(-) rename dev/compojure_api_kondo_hooks/compojure/{api.clj => core.clj} (100%) create mode 100644 examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj diff --git a/dev/compojure_api_kondo_hooks/compojure/api.clj b/dev/compojure_api_kondo_hooks/compojure/core.clj similarity index 100% rename from dev/compojure_api_kondo_hooks/compojure/api.clj rename to dev/compojure_api_kondo_hooks/compojure/core.clj diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj new file mode 100644 index 00000000..245cf85a --- /dev/null +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj @@ -0,0 +1,353 @@ +;; Copyright © 2024 James Reeves +;; +;; Distributed under the Eclipse Public License, the same as Clojure. +(ns compojure-api-kondo-hooks.compojure.core + (:require [compojure.response :as response] + [clojure.tools.macro :as macro] + [clout.core :as clout] + [ring.middleware.head :refer [wrap-head]] + [ring.util.codec :as codec] + [medley.core :refer [map-vals]])) + +(defn- form-method [request] + (or (get-in request [:form-params "_method"]) + (get-in request [:multipart-params "_method"]))) + +(defn- method-matches? [request method] + (or (nil? method) + (let [request-method (:request-method request)] + (case request-method + :head + (or (= method :head) (= method :get)) + :post + (if-let [fm (form-method request)] + (.equalsIgnoreCase (name method) fm) + (= method :post)) + ;else + (= request-method method))))) + +(defn- head-response [response request method] + (if (and response (= :get method) (= :head (:request-method request))) + (assoc response :body nil) + response)) + +(defn- decode-route-params [params] + (map-vals codec/url-decode params)) + +(defn- assoc-route-params [request params] + (merge-with merge request {:route-params params, :params params})) + +(defn- route-matches [route request] + (let [path (:compojure/path request)] + (clout/route-matches route (cond-> request path (assoc :path-info path))))) + +(defn- route-request [request route] + (if-let [params (route-matches route request)] + (assoc-route-params request (decode-route-params params)))) + +(defn- literal? [x] + (if (coll? x) + (every? literal? x) + (not (or (symbol? x) (list? x))))) + +(defn- prepare-route [route] + (cond + (string? route) + (clout/route-compile route) + (and (vector? route) (literal? route)) + (clout/route-compile + (first route) + (apply hash-map (rest route))) + (vector? route) + `(clout/route-compile + ~(first route) + ~(apply hash-map (rest route))) + :else + `(if (string? ~route) + (clout/route-compile ~route) + ~route))) + +(defn- and-binding [req binds] + `(dissoc (:params ~req) ~@(map keyword (keys binds)) ~@(map str (keys binds)))) + +(defn- symbol-binding [req sym] + `(get-in ~req [:params ~(keyword sym)] (get-in ~req [:params ~(str sym)]))) + +(defn- application-binding [req sym func] + `(~func ~(symbol-binding req sym))) + +(defn- vector-bindings [args req] + (loop [args args, binds {}] + (if (seq args) + (let [[x y z] args] + (cond + (= '& x) + (recur (nnext args) (assoc binds y (and-binding req binds))) + (= :as x) + (recur (nnext args) (assoc binds y req)) + (and (symbol? x) (= :<< y) (nnext args)) + (recur (drop 3 args) (assoc binds x (application-binding req x z))) + (symbol? x) + (recur (next args) (assoc binds x (symbol-binding req x))) + :else + (throw (Exception. (str "Unexpected binding: " x))))) + (mapcat identity binds)))) + +(defn- warn-on-*-bindings! [bindings] + (when (and (vector? bindings) (contains? (set bindings) '*)) + (binding [*out* *err*] + (println "WARNING: * should not be used as a route binding.")))) + +(defn- application-symbols [args] + (loop [args args, syms '()] + (if (seq args) + (let [[x y] args] + (if (and (symbol? x) (= :<< y)) + (recur (drop 3 args) (conj syms x)) + (recur (next args) syms))) + (seq syms)))) + +(defmacro ^:no-doc let-request [[bindings request] & body] + (warn-on-*-bindings! bindings) + (if (vector? bindings) + `(let [~@(vector-bindings bindings request)] + ~(if-let [syms (application-symbols bindings)] + `(if (and ~@(for [s syms] `(not (nil? ~s)))) (do ~@body)) + `(do ~@body))) + `(let [~bindings ~request] ~@body))) + +(defn- wrap-route-middleware [handler] + (fn + ([request] + (if-let [mw (:route-middleware request)] + ((mw handler) request) + (handler request))) + ([request respond raise] + (if-let [mw (:route-middleware request)] + ((mw handler) request respond raise) + (handler request respond raise))))) + +(defn- wrap-route-info [handler route-info] + (fn + ([request] + (handler (assoc request :compojure/route route-info))) + ([request respond raise] + (handler (assoc request :compojure/route route-info) respond raise)))) + +(defn- wrap-route-matches [handler method path] + (fn + ([request] + (if (method-matches? request method) + (if-let [request (route-request request path)] + (-> (handler request) + (head-response request method))))) + ([request respond raise] + (if (method-matches? request method) + (if-let [request (route-request request path)] + (handler request #(respond (head-response % request method)) raise) + (respond nil)) + (respond nil))))) + +(defn- wrap-response [handler] + (fn + ([request] + (response/render (handler request) request)) + ([request respond raise] + (response/send (handler request) request respond raise)))) + +(defn make-route + "Returns a function that will only call the handler if the method and path + match the request." + [method path handler] + (-> handler + (wrap-response) + (wrap-route-middleware) + (wrap-route-info [(or method :any) (str path)]) + (wrap-route-matches method path))) + +(defn compile-route + "Compile a route in the form `(method path bindings & body)` into a function. + Used to create custom route macros." + [method path bindings body] + `(make-route + ~method + ~(prepare-route path) + (fn [request#] + (let-request [~bindings request#] ~@body)))) + +(defn routing + "Apply a list of routes to a Ring request map." + [request & handlers] + (some #(% request) handlers)) + +(defn routes + "Create a Ring handler by combining several handlers into one." + [& handlers] + (fn + ([request] + (apply routing request handlers)) + ([request respond raise] + (letfn [(f [handlers] + (if (seq handlers) + (let [handler (first handlers) + respond' #(if % (respond %) (f (rest handlers)))] + (handler request respond' raise)) + (respond nil)))] + (f handlers))))) + +(defmacro defroutes + "Define a Ring handler function from a sequence of routes. The name may + optionally be followed by a doc-string and metadata map." + [name & routes] + (let [[name routes] (macro/name-with-attributes name routes)] + `(def ~name (routes ~@routes)))) + +(defmacro GET "Generate a `GET` route." + [path args & body] + (compile-route :get path args body)) + +(defmacro POST "Generate a `POST` route." + [path args & body] + (compile-route :post path args body)) + +(defmacro PUT "Generate a `PUT` route." + [path args & body] + (compile-route :put path args body)) + +(defmacro DELETE "Generate a `DELETE` route." + [path args & body] + (compile-route :delete path args body)) + +(defmacro HEAD "Generate a `HEAD` route." + [path args & body] + (compile-route :head path args body)) + +(defmacro OPTIONS "Generate an `OPTIONS` route." + [path args & body] + (compile-route :options path args body)) + +(defmacro PATCH "Generate a `PATCH` route." + [path args & body] + (compile-route :patch path args body)) + +(defmacro ANY "Generate a route that matches any method." + [path args & body] + (compile-route nil path args body)) + +(defn ^:no-doc make-rfn [handler] + (-> handler + wrap-response + wrap-route-middleware + wrap-head)) + +(defmacro rfn "Generate a route that matches any method and path." + [args & body] + `(make-rfn (fn [request#] (let-request [~args request#] ~@body)))) + +(defn- remove-suffix [path suffix] + (subs path 0 (- (count path) (count suffix)))) + +(defn- context-request [request route route-context] + (if-let [params (clout/route-matches route request)] + (let [uri (:uri request) + path (:path-info request uri) + context (or (:context request) "") + subpath (:__path-info params) + params (dissoc params :__path-info)] + (-> request + (assoc-route-params (decode-route-params params)) + (assoc :path-info (if (= subpath "") "/" subpath) + :context (remove-suffix uri subpath)) + (update :compojure/route-context str route-context))))) + +(defn- context-route [route] + (let [re-context {:__path-info #"|/.*"}] + (cond + (string? route) + (clout/route-compile (str route ":__path-info") re-context) + (and (vector? route) (literal? route)) + (clout/route-compile + (str (first route) ":__path-info") + (merge (apply hash-map (rest route)) re-context)) + (vector? route) + `(clout/route-compile + (str ~(first route) ":__path-info") + ~(merge (apply hash-map (rest route)) re-context)) + :else + `(clout/route-compile (str ~route ":__path-info") ~re-context)))) + +(defn ^:no-doc make-context [route path make-handler] + (letfn [(handler + ([request] + (when-let [context-handler (make-handler request)] + (context-handler request))) + ([request respond raise] + (if-let [context-handler (make-handler request)] + (context-handler request respond raise) + (respond nil))))] + (if (#{":__path-info" "/:__path-info"} (:source route)) + handler + (fn + ([request] + (if-let [request (context-request request route path)] + (handler request))) + ([request respond raise] + (if-let [request (context-request request route path)] + (handler request respond raise) + (respond nil))))))) + +(defmacro context + "Give all routes in the form a common path prefix and set of bindings. + + The following example demonstrates defining two routes with a common + path prefix ('/user/:id') and a common binding ('id'): + + (context \"/user/:id\" [id] + (GET \"/profile\" [] ...) + (GET \"/settings\" [] ...))" + [path args & routes] + `(make-context + ~(context-route path) + ~path + (fn [request#] + (let-request [~args request#] + (routes ~@routes))))) + +(defmacro let-routes + "Takes a vector of bindings and a body of routes. + + Equivalent to: + + (let [...] (routes ...))" + [bindings & body] + `(let ~bindings (routes ~@body))) + +(defn- pre-init [middleware] + (let [proxy (middleware + (fn + ([request] + ((:route-handler request) request)) + ([request respond raise] + ((:route-handler request) request respond raise))))] + (fn [handler] + (let [prep-request #(assoc % :route-handler handler)] + (fn + ([request] + (proxy (prep-request request))) + ([request respond raise] + (proxy (prep-request request) respond raise))))))) + +(defn wrap-routes + "Apply a middleware function to routes after they have been matched." + ([handler middleware] + (let [middleware (pre-init middleware) + prep-request (fn [request] + (let [mw (:route-middleware request identity)] + (assoc request :route-middleware (comp mw middleware))))] + (fn + ([request] + (handler (prep-request request))) + ([request respond raise] + (handler (prep-request request) respond raise))))) + ([handler middleware & args] + (wrap-routes handler #(apply middleware % args)))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj new file mode 100644 index 00000000..245cf85a --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj @@ -0,0 +1,353 @@ +;; Copyright © 2024 James Reeves +;; +;; Distributed under the Eclipse Public License, the same as Clojure. +(ns compojure-api-kondo-hooks.compojure.core + (:require [compojure.response :as response] + [clojure.tools.macro :as macro] + [clout.core :as clout] + [ring.middleware.head :refer [wrap-head]] + [ring.util.codec :as codec] + [medley.core :refer [map-vals]])) + +(defn- form-method [request] + (or (get-in request [:form-params "_method"]) + (get-in request [:multipart-params "_method"]))) + +(defn- method-matches? [request method] + (or (nil? method) + (let [request-method (:request-method request)] + (case request-method + :head + (or (= method :head) (= method :get)) + :post + (if-let [fm (form-method request)] + (.equalsIgnoreCase (name method) fm) + (= method :post)) + ;else + (= request-method method))))) + +(defn- head-response [response request method] + (if (and response (= :get method) (= :head (:request-method request))) + (assoc response :body nil) + response)) + +(defn- decode-route-params [params] + (map-vals codec/url-decode params)) + +(defn- assoc-route-params [request params] + (merge-with merge request {:route-params params, :params params})) + +(defn- route-matches [route request] + (let [path (:compojure/path request)] + (clout/route-matches route (cond-> request path (assoc :path-info path))))) + +(defn- route-request [request route] + (if-let [params (route-matches route request)] + (assoc-route-params request (decode-route-params params)))) + +(defn- literal? [x] + (if (coll? x) + (every? literal? x) + (not (or (symbol? x) (list? x))))) + +(defn- prepare-route [route] + (cond + (string? route) + (clout/route-compile route) + (and (vector? route) (literal? route)) + (clout/route-compile + (first route) + (apply hash-map (rest route))) + (vector? route) + `(clout/route-compile + ~(first route) + ~(apply hash-map (rest route))) + :else + `(if (string? ~route) + (clout/route-compile ~route) + ~route))) + +(defn- and-binding [req binds] + `(dissoc (:params ~req) ~@(map keyword (keys binds)) ~@(map str (keys binds)))) + +(defn- symbol-binding [req sym] + `(get-in ~req [:params ~(keyword sym)] (get-in ~req [:params ~(str sym)]))) + +(defn- application-binding [req sym func] + `(~func ~(symbol-binding req sym))) + +(defn- vector-bindings [args req] + (loop [args args, binds {}] + (if (seq args) + (let [[x y z] args] + (cond + (= '& x) + (recur (nnext args) (assoc binds y (and-binding req binds))) + (= :as x) + (recur (nnext args) (assoc binds y req)) + (and (symbol? x) (= :<< y) (nnext args)) + (recur (drop 3 args) (assoc binds x (application-binding req x z))) + (symbol? x) + (recur (next args) (assoc binds x (symbol-binding req x))) + :else + (throw (Exception. (str "Unexpected binding: " x))))) + (mapcat identity binds)))) + +(defn- warn-on-*-bindings! [bindings] + (when (and (vector? bindings) (contains? (set bindings) '*)) + (binding [*out* *err*] + (println "WARNING: * should not be used as a route binding.")))) + +(defn- application-symbols [args] + (loop [args args, syms '()] + (if (seq args) + (let [[x y] args] + (if (and (symbol? x) (= :<< y)) + (recur (drop 3 args) (conj syms x)) + (recur (next args) syms))) + (seq syms)))) + +(defmacro ^:no-doc let-request [[bindings request] & body] + (warn-on-*-bindings! bindings) + (if (vector? bindings) + `(let [~@(vector-bindings bindings request)] + ~(if-let [syms (application-symbols bindings)] + `(if (and ~@(for [s syms] `(not (nil? ~s)))) (do ~@body)) + `(do ~@body))) + `(let [~bindings ~request] ~@body))) + +(defn- wrap-route-middleware [handler] + (fn + ([request] + (if-let [mw (:route-middleware request)] + ((mw handler) request) + (handler request))) + ([request respond raise] + (if-let [mw (:route-middleware request)] + ((mw handler) request respond raise) + (handler request respond raise))))) + +(defn- wrap-route-info [handler route-info] + (fn + ([request] + (handler (assoc request :compojure/route route-info))) + ([request respond raise] + (handler (assoc request :compojure/route route-info) respond raise)))) + +(defn- wrap-route-matches [handler method path] + (fn + ([request] + (if (method-matches? request method) + (if-let [request (route-request request path)] + (-> (handler request) + (head-response request method))))) + ([request respond raise] + (if (method-matches? request method) + (if-let [request (route-request request path)] + (handler request #(respond (head-response % request method)) raise) + (respond nil)) + (respond nil))))) + +(defn- wrap-response [handler] + (fn + ([request] + (response/render (handler request) request)) + ([request respond raise] + (response/send (handler request) request respond raise)))) + +(defn make-route + "Returns a function that will only call the handler if the method and path + match the request." + [method path handler] + (-> handler + (wrap-response) + (wrap-route-middleware) + (wrap-route-info [(or method :any) (str path)]) + (wrap-route-matches method path))) + +(defn compile-route + "Compile a route in the form `(method path bindings & body)` into a function. + Used to create custom route macros." + [method path bindings body] + `(make-route + ~method + ~(prepare-route path) + (fn [request#] + (let-request [~bindings request#] ~@body)))) + +(defn routing + "Apply a list of routes to a Ring request map." + [request & handlers] + (some #(% request) handlers)) + +(defn routes + "Create a Ring handler by combining several handlers into one." + [& handlers] + (fn + ([request] + (apply routing request handlers)) + ([request respond raise] + (letfn [(f [handlers] + (if (seq handlers) + (let [handler (first handlers) + respond' #(if % (respond %) (f (rest handlers)))] + (handler request respond' raise)) + (respond nil)))] + (f handlers))))) + +(defmacro defroutes + "Define a Ring handler function from a sequence of routes. The name may + optionally be followed by a doc-string and metadata map." + [name & routes] + (let [[name routes] (macro/name-with-attributes name routes)] + `(def ~name (routes ~@routes)))) + +(defmacro GET "Generate a `GET` route." + [path args & body] + (compile-route :get path args body)) + +(defmacro POST "Generate a `POST` route." + [path args & body] + (compile-route :post path args body)) + +(defmacro PUT "Generate a `PUT` route." + [path args & body] + (compile-route :put path args body)) + +(defmacro DELETE "Generate a `DELETE` route." + [path args & body] + (compile-route :delete path args body)) + +(defmacro HEAD "Generate a `HEAD` route." + [path args & body] + (compile-route :head path args body)) + +(defmacro OPTIONS "Generate an `OPTIONS` route." + [path args & body] + (compile-route :options path args body)) + +(defmacro PATCH "Generate a `PATCH` route." + [path args & body] + (compile-route :patch path args body)) + +(defmacro ANY "Generate a route that matches any method." + [path args & body] + (compile-route nil path args body)) + +(defn ^:no-doc make-rfn [handler] + (-> handler + wrap-response + wrap-route-middleware + wrap-head)) + +(defmacro rfn "Generate a route that matches any method and path." + [args & body] + `(make-rfn (fn [request#] (let-request [~args request#] ~@body)))) + +(defn- remove-suffix [path suffix] + (subs path 0 (- (count path) (count suffix)))) + +(defn- context-request [request route route-context] + (if-let [params (clout/route-matches route request)] + (let [uri (:uri request) + path (:path-info request uri) + context (or (:context request) "") + subpath (:__path-info params) + params (dissoc params :__path-info)] + (-> request + (assoc-route-params (decode-route-params params)) + (assoc :path-info (if (= subpath "") "/" subpath) + :context (remove-suffix uri subpath)) + (update :compojure/route-context str route-context))))) + +(defn- context-route [route] + (let [re-context {:__path-info #"|/.*"}] + (cond + (string? route) + (clout/route-compile (str route ":__path-info") re-context) + (and (vector? route) (literal? route)) + (clout/route-compile + (str (first route) ":__path-info") + (merge (apply hash-map (rest route)) re-context)) + (vector? route) + `(clout/route-compile + (str ~(first route) ":__path-info") + ~(merge (apply hash-map (rest route)) re-context)) + :else + `(clout/route-compile (str ~route ":__path-info") ~re-context)))) + +(defn ^:no-doc make-context [route path make-handler] + (letfn [(handler + ([request] + (when-let [context-handler (make-handler request)] + (context-handler request))) + ([request respond raise] + (if-let [context-handler (make-handler request)] + (context-handler request respond raise) + (respond nil))))] + (if (#{":__path-info" "/:__path-info"} (:source route)) + handler + (fn + ([request] + (if-let [request (context-request request route path)] + (handler request))) + ([request respond raise] + (if-let [request (context-request request route path)] + (handler request respond raise) + (respond nil))))))) + +(defmacro context + "Give all routes in the form a common path prefix and set of bindings. + + The following example demonstrates defining two routes with a common + path prefix ('/user/:id') and a common binding ('id'): + + (context \"/user/:id\" [id] + (GET \"/profile\" [] ...) + (GET \"/settings\" [] ...))" + [path args & routes] + `(make-context + ~(context-route path) + ~path + (fn [request#] + (let-request [~args request#] + (routes ~@routes))))) + +(defmacro let-routes + "Takes a vector of bindings and a body of routes. + + Equivalent to: + + (let [...] (routes ...))" + [bindings & body] + `(let ~bindings (routes ~@body))) + +(defn- pre-init [middleware] + (let [proxy (middleware + (fn + ([request] + ((:route-handler request) request)) + ([request respond raise] + ((:route-handler request) request respond raise))))] + (fn [handler] + (let [prep-request #(assoc % :route-handler handler)] + (fn + ([request] + (proxy (prep-request request))) + ([request respond raise] + (proxy (prep-request request) respond raise))))))) + +(defn wrap-routes + "Apply a middleware function to routes after they have been matched." + ([handler middleware] + (let [middleware (pre-init middleware) + prep-request (fn [request] + (let [mw (:route-middleware request identity)] + (assoc request :route-middleware (comp mw middleware))))] + (fn + ([request] + (handler (prep-request request))) + ([request respond raise] + (handler (prep-request request) respond raise))))) + ([handler middleware & args] + (wrap-routes handler #(apply middleware % args)))) diff --git a/scripts/regen-kondo.clj b/scripts/regen-kondo.clj index 01e946d6..c9bdbfa3 100755 --- a/scripts/regen-kondo.clj +++ b/scripts/regen-kondo.clj @@ -4,5 +4,6 @@ set -ex rm -r resources/clj-kondo.exports mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure/api +mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure bb -f ./scripts/regen_kondo_config.clj diff --git a/scripts/regen_kondo_config.clj b/scripts/regen_kondo_config.clj index 7a129694..880fd496 100755 --- a/scripts/regen_kondo_config.clj +++ b/scripts/regen_kondo_config.clj @@ -7,7 +7,8 @@ ;; rename to .clj {"src/compojure/api/common.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj" "src/compojure/api/core.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj" - "src/compojure/api/meta.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj"}) + "src/compojure/api/meta.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj" + "dev/compojure_api_kondo_hooks/compojure/core.clj" "resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj"}) (defn -main [& args] @@ -18,6 +19,7 @@ '{:linters {:unresolved-namespace {:exclude [(compojure.api.routes)]}} :hooks {:macroexpand - {compojure.api.core/GET compojure.api.core/GET}}})) + {compojure.api.core/GET compojure.api.core/GET + }}})) (when (= *file* (System/getProperty "babashka.file")) (-main)) From b37d3fb1533d8b72ea3d9d8186952500694b0c43 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 16:52:33 -0500 Subject: [PATCH 28/39] wip --- .../compojure/core.clj | 301 +----------------- .../compojure/core.clj | 301 +----------------- .../compojure/core.clj | 301 +----------------- 3 files changed, 12 insertions(+), 891 deletions(-) diff --git a/dev/compojure_api_kondo_hooks/compojure/core.clj b/dev/compojure_api_kondo_hooks/compojure/core.clj index 245cf85a..7b95c17b 100644 --- a/dev/compojure_api_kondo_hooks/compojure/core.clj +++ b/dev/compojure_api_kondo_hooks/compojure/core.clj @@ -1,71 +1,7 @@ ;; Copyright © 2024 James Reeves ;; ;; Distributed under the Eclipse Public License, the same as Clojure. -(ns compojure-api-kondo-hooks.compojure.core - (:require [compojure.response :as response] - [clojure.tools.macro :as macro] - [clout.core :as clout] - [ring.middleware.head :refer [wrap-head]] - [ring.util.codec :as codec] - [medley.core :refer [map-vals]])) - -(defn- form-method [request] - (or (get-in request [:form-params "_method"]) - (get-in request [:multipart-params "_method"]))) - -(defn- method-matches? [request method] - (or (nil? method) - (let [request-method (:request-method request)] - (case request-method - :head - (or (= method :head) (= method :get)) - :post - (if-let [fm (form-method request)] - (.equalsIgnoreCase (name method) fm) - (= method :post)) - ;else - (= request-method method))))) - -(defn- head-response [response request method] - (if (and response (= :get method) (= :head (:request-method request))) - (assoc response :body nil) - response)) - -(defn- decode-route-params [params] - (map-vals codec/url-decode params)) - -(defn- assoc-route-params [request params] - (merge-with merge request {:route-params params, :params params})) - -(defn- route-matches [route request] - (let [path (:compojure/path request)] - (clout/route-matches route (cond-> request path (assoc :path-info path))))) - -(defn- route-request [request route] - (if-let [params (route-matches route request)] - (assoc-route-params request (decode-route-params params)))) - -(defn- literal? [x] - (if (coll? x) - (every? literal? x) - (not (or (symbol? x) (list? x))))) - -(defn- prepare-route [route] - (cond - (string? route) - (clout/route-compile route) - (and (vector? route) (literal? route)) - (clout/route-compile - (first route) - (apply hash-map (rest route))) - (vector? route) - `(clout/route-compile - ~(first route) - ~(apply hash-map (rest route))) - :else - `(if (string? ~route) - (clout/route-compile ~route) - ~route))) +(ns compojure-api-kondo-hooks.compojure.core) (defn- and-binding [req binds] `(dissoc (:params ~req) ~@(map keyword (keys binds)) ~@(map str (keys binds)))) @@ -108,7 +44,6 @@ (seq syms)))) (defmacro ^:no-doc let-request [[bindings request] & body] - (warn-on-*-bindings! bindings) (if (vector? bindings) `(let [~@(vector-bindings bindings request)] ~(if-let [syms (application-symbols bindings)] @@ -116,238 +51,10 @@ `(do ~@body))) `(let [~bindings ~request] ~@body))) -(defn- wrap-route-middleware [handler] - (fn - ([request] - (if-let [mw (:route-middleware request)] - ((mw handler) request) - (handler request))) - ([request respond raise] - (if-let [mw (:route-middleware request)] - ((mw handler) request respond raise) - (handler request respond raise))))) - -(defn- wrap-route-info [handler route-info] - (fn - ([request] - (handler (assoc request :compojure/route route-info))) - ([request respond raise] - (handler (assoc request :compojure/route route-info) respond raise)))) - -(defn- wrap-route-matches [handler method path] - (fn - ([request] - (if (method-matches? request method) - (if-let [request (route-request request path)] - (-> (handler request) - (head-response request method))))) - ([request respond raise] - (if (method-matches? request method) - (if-let [request (route-request request path)] - (handler request #(respond (head-response % request method)) raise) - (respond nil)) - (respond nil))))) - -(defn- wrap-response [handler] - (fn - ([request] - (response/render (handler request) request)) - ([request respond raise] - (response/send (handler request) request respond raise)))) - -(defn make-route - "Returns a function that will only call the handler if the method and path - match the request." - [method path handler] - (-> handler - (wrap-response) - (wrap-route-middleware) - (wrap-route-info [(or method :any) (str path)]) - (wrap-route-matches method path))) - (defn compile-route "Compile a route in the form `(method path bindings & body)` into a function. Used to create custom route macros." [method path bindings body] - `(make-route - ~method - ~(prepare-route path) - (fn [request#] - (let-request [~bindings request#] ~@body)))) - -(defn routing - "Apply a list of routes to a Ring request map." - [request & handlers] - (some #(% request) handlers)) - -(defn routes - "Create a Ring handler by combining several handlers into one." - [& handlers] - (fn - ([request] - (apply routing request handlers)) - ([request respond raise] - (letfn [(f [handlers] - (if (seq handlers) - (let [handler (first handlers) - respond' #(if % (respond %) (f (rest handlers)))] - (handler request respond' raise)) - (respond nil)))] - (f handlers))))) - -(defmacro defroutes - "Define a Ring handler function from a sequence of routes. The name may - optionally be followed by a doc-string and metadata map." - [name & routes] - (let [[name routes] (macro/name-with-attributes name routes)] - `(def ~name (routes ~@routes)))) - -(defmacro GET "Generate a `GET` route." - [path args & body] - (compile-route :get path args body)) - -(defmacro POST "Generate a `POST` route." - [path args & body] - (compile-route :post path args body)) - -(defmacro PUT "Generate a `PUT` route." - [path args & body] - (compile-route :put path args body)) - -(defmacro DELETE "Generate a `DELETE` route." - [path args & body] - (compile-route :delete path args body)) - -(defmacro HEAD "Generate a `HEAD` route." - [path args & body] - (compile-route :head path args body)) - -(defmacro OPTIONS "Generate an `OPTIONS` route." - [path args & body] - (compile-route :options path args body)) - -(defmacro PATCH "Generate a `PATCH` route." - [path args & body] - (compile-route :patch path args body)) - -(defmacro ANY "Generate a route that matches any method." - [path args & body] - (compile-route nil path args body)) - -(defn ^:no-doc make-rfn [handler] - (-> handler - wrap-response - wrap-route-middleware - wrap-head)) - -(defmacro rfn "Generate a route that matches any method and path." - [args & body] - `(make-rfn (fn [request#] (let-request [~args request#] ~@body)))) - -(defn- remove-suffix [path suffix] - (subs path 0 (- (count path) (count suffix)))) - -(defn- context-request [request route route-context] - (if-let [params (clout/route-matches route request)] - (let [uri (:uri request) - path (:path-info request uri) - context (or (:context request) "") - subpath (:__path-info params) - params (dissoc params :__path-info)] - (-> request - (assoc-route-params (decode-route-params params)) - (assoc :path-info (if (= subpath "") "/" subpath) - :context (remove-suffix uri subpath)) - (update :compojure/route-context str route-context))))) - -(defn- context-route [route] - (let [re-context {:__path-info #"|/.*"}] - (cond - (string? route) - (clout/route-compile (str route ":__path-info") re-context) - (and (vector? route) (literal? route)) - (clout/route-compile - (str (first route) ":__path-info") - (merge (apply hash-map (rest route)) re-context)) - (vector? route) - `(clout/route-compile - (str ~(first route) ":__path-info") - ~(merge (apply hash-map (rest route)) re-context)) - :else - `(clout/route-compile (str ~route ":__path-info") ~re-context)))) - -(defn ^:no-doc make-context [route path make-handler] - (letfn [(handler - ([request] - (when-let [context-handler (make-handler request)] - (context-handler request))) - ([request respond raise] - (if-let [context-handler (make-handler request)] - (context-handler request respond raise) - (respond nil))))] - (if (#{":__path-info" "/:__path-info"} (:source route)) - handler - (fn - ([request] - (if-let [request (context-request request route path)] - (handler request))) - ([request respond raise] - (if-let [request (context-request request route path)] - (handler request respond raise) - (respond nil))))))) - -(defmacro context - "Give all routes in the form a common path prefix and set of bindings. - - The following example demonstrates defining two routes with a common - path prefix ('/user/:id') and a common binding ('id'): - - (context \"/user/:id\" [id] - (GET \"/profile\" [] ...) - (GET \"/settings\" [] ...))" - [path args & routes] - `(make-context - ~(context-route path) - ~path - (fn [request#] - (let-request [~args request#] - (routes ~@routes))))) - -(defmacro let-routes - "Takes a vector of bindings and a body of routes. - - Equivalent to: - - (let [...] (routes ...))" - [bindings & body] - `(let ~bindings (routes ~@body))) - -(defn- pre-init [middleware] - (let [proxy (middleware - (fn - ([request] - ((:route-handler request) request)) - ([request respond raise] - ((:route-handler request) request respond raise))))] - (fn [handler] - (let [prep-request #(assoc % :route-handler handler)] - (fn - ([request] - (proxy (prep-request request))) - ([request respond raise] - (proxy (prep-request request) respond raise))))))) - -(defn wrap-routes - "Apply a middleware function to routes after they have been matched." - ([handler middleware] - (let [middleware (pre-init middleware) - prep-request (fn [request] - (let [mw (:route-middleware request identity)] - (assoc request :route-middleware (comp mw middleware))))] - (fn - ([request] - (handler (prep-request request))) - ([request respond raise] - (handler (prep-request request) respond raise))))) - ([handler middleware & args] - (wrap-routes handler #(apply middleware % args)))) + (let [greq (gensym "greq")] + `(fn [~greq] + ~(macroexpand-1 `(let-request [~bindings ~greq] ~@body))))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj index 245cf85a..7b95c17b 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj @@ -1,71 +1,7 @@ ;; Copyright © 2024 James Reeves ;; ;; Distributed under the Eclipse Public License, the same as Clojure. -(ns compojure-api-kondo-hooks.compojure.core - (:require [compojure.response :as response] - [clojure.tools.macro :as macro] - [clout.core :as clout] - [ring.middleware.head :refer [wrap-head]] - [ring.util.codec :as codec] - [medley.core :refer [map-vals]])) - -(defn- form-method [request] - (or (get-in request [:form-params "_method"]) - (get-in request [:multipart-params "_method"]))) - -(defn- method-matches? [request method] - (or (nil? method) - (let [request-method (:request-method request)] - (case request-method - :head - (or (= method :head) (= method :get)) - :post - (if-let [fm (form-method request)] - (.equalsIgnoreCase (name method) fm) - (= method :post)) - ;else - (= request-method method))))) - -(defn- head-response [response request method] - (if (and response (= :get method) (= :head (:request-method request))) - (assoc response :body nil) - response)) - -(defn- decode-route-params [params] - (map-vals codec/url-decode params)) - -(defn- assoc-route-params [request params] - (merge-with merge request {:route-params params, :params params})) - -(defn- route-matches [route request] - (let [path (:compojure/path request)] - (clout/route-matches route (cond-> request path (assoc :path-info path))))) - -(defn- route-request [request route] - (if-let [params (route-matches route request)] - (assoc-route-params request (decode-route-params params)))) - -(defn- literal? [x] - (if (coll? x) - (every? literal? x) - (not (or (symbol? x) (list? x))))) - -(defn- prepare-route [route] - (cond - (string? route) - (clout/route-compile route) - (and (vector? route) (literal? route)) - (clout/route-compile - (first route) - (apply hash-map (rest route))) - (vector? route) - `(clout/route-compile - ~(first route) - ~(apply hash-map (rest route))) - :else - `(if (string? ~route) - (clout/route-compile ~route) - ~route))) +(ns compojure-api-kondo-hooks.compojure.core) (defn- and-binding [req binds] `(dissoc (:params ~req) ~@(map keyword (keys binds)) ~@(map str (keys binds)))) @@ -108,7 +44,6 @@ (seq syms)))) (defmacro ^:no-doc let-request [[bindings request] & body] - (warn-on-*-bindings! bindings) (if (vector? bindings) `(let [~@(vector-bindings bindings request)] ~(if-let [syms (application-symbols bindings)] @@ -116,238 +51,10 @@ `(do ~@body))) `(let [~bindings ~request] ~@body))) -(defn- wrap-route-middleware [handler] - (fn - ([request] - (if-let [mw (:route-middleware request)] - ((mw handler) request) - (handler request))) - ([request respond raise] - (if-let [mw (:route-middleware request)] - ((mw handler) request respond raise) - (handler request respond raise))))) - -(defn- wrap-route-info [handler route-info] - (fn - ([request] - (handler (assoc request :compojure/route route-info))) - ([request respond raise] - (handler (assoc request :compojure/route route-info) respond raise)))) - -(defn- wrap-route-matches [handler method path] - (fn - ([request] - (if (method-matches? request method) - (if-let [request (route-request request path)] - (-> (handler request) - (head-response request method))))) - ([request respond raise] - (if (method-matches? request method) - (if-let [request (route-request request path)] - (handler request #(respond (head-response % request method)) raise) - (respond nil)) - (respond nil))))) - -(defn- wrap-response [handler] - (fn - ([request] - (response/render (handler request) request)) - ([request respond raise] - (response/send (handler request) request respond raise)))) - -(defn make-route - "Returns a function that will only call the handler if the method and path - match the request." - [method path handler] - (-> handler - (wrap-response) - (wrap-route-middleware) - (wrap-route-info [(or method :any) (str path)]) - (wrap-route-matches method path))) - (defn compile-route "Compile a route in the form `(method path bindings & body)` into a function. Used to create custom route macros." [method path bindings body] - `(make-route - ~method - ~(prepare-route path) - (fn [request#] - (let-request [~bindings request#] ~@body)))) - -(defn routing - "Apply a list of routes to a Ring request map." - [request & handlers] - (some #(% request) handlers)) - -(defn routes - "Create a Ring handler by combining several handlers into one." - [& handlers] - (fn - ([request] - (apply routing request handlers)) - ([request respond raise] - (letfn [(f [handlers] - (if (seq handlers) - (let [handler (first handlers) - respond' #(if % (respond %) (f (rest handlers)))] - (handler request respond' raise)) - (respond nil)))] - (f handlers))))) - -(defmacro defroutes - "Define a Ring handler function from a sequence of routes. The name may - optionally be followed by a doc-string and metadata map." - [name & routes] - (let [[name routes] (macro/name-with-attributes name routes)] - `(def ~name (routes ~@routes)))) - -(defmacro GET "Generate a `GET` route." - [path args & body] - (compile-route :get path args body)) - -(defmacro POST "Generate a `POST` route." - [path args & body] - (compile-route :post path args body)) - -(defmacro PUT "Generate a `PUT` route." - [path args & body] - (compile-route :put path args body)) - -(defmacro DELETE "Generate a `DELETE` route." - [path args & body] - (compile-route :delete path args body)) - -(defmacro HEAD "Generate a `HEAD` route." - [path args & body] - (compile-route :head path args body)) - -(defmacro OPTIONS "Generate an `OPTIONS` route." - [path args & body] - (compile-route :options path args body)) - -(defmacro PATCH "Generate a `PATCH` route." - [path args & body] - (compile-route :patch path args body)) - -(defmacro ANY "Generate a route that matches any method." - [path args & body] - (compile-route nil path args body)) - -(defn ^:no-doc make-rfn [handler] - (-> handler - wrap-response - wrap-route-middleware - wrap-head)) - -(defmacro rfn "Generate a route that matches any method and path." - [args & body] - `(make-rfn (fn [request#] (let-request [~args request#] ~@body)))) - -(defn- remove-suffix [path suffix] - (subs path 0 (- (count path) (count suffix)))) - -(defn- context-request [request route route-context] - (if-let [params (clout/route-matches route request)] - (let [uri (:uri request) - path (:path-info request uri) - context (or (:context request) "") - subpath (:__path-info params) - params (dissoc params :__path-info)] - (-> request - (assoc-route-params (decode-route-params params)) - (assoc :path-info (if (= subpath "") "/" subpath) - :context (remove-suffix uri subpath)) - (update :compojure/route-context str route-context))))) - -(defn- context-route [route] - (let [re-context {:__path-info #"|/.*"}] - (cond - (string? route) - (clout/route-compile (str route ":__path-info") re-context) - (and (vector? route) (literal? route)) - (clout/route-compile - (str (first route) ":__path-info") - (merge (apply hash-map (rest route)) re-context)) - (vector? route) - `(clout/route-compile - (str ~(first route) ":__path-info") - ~(merge (apply hash-map (rest route)) re-context)) - :else - `(clout/route-compile (str ~route ":__path-info") ~re-context)))) - -(defn ^:no-doc make-context [route path make-handler] - (letfn [(handler - ([request] - (when-let [context-handler (make-handler request)] - (context-handler request))) - ([request respond raise] - (if-let [context-handler (make-handler request)] - (context-handler request respond raise) - (respond nil))))] - (if (#{":__path-info" "/:__path-info"} (:source route)) - handler - (fn - ([request] - (if-let [request (context-request request route path)] - (handler request))) - ([request respond raise] - (if-let [request (context-request request route path)] - (handler request respond raise) - (respond nil))))))) - -(defmacro context - "Give all routes in the form a common path prefix and set of bindings. - - The following example demonstrates defining two routes with a common - path prefix ('/user/:id') and a common binding ('id'): - - (context \"/user/:id\" [id] - (GET \"/profile\" [] ...) - (GET \"/settings\" [] ...))" - [path args & routes] - `(make-context - ~(context-route path) - ~path - (fn [request#] - (let-request [~args request#] - (routes ~@routes))))) - -(defmacro let-routes - "Takes a vector of bindings and a body of routes. - - Equivalent to: - - (let [...] (routes ...))" - [bindings & body] - `(let ~bindings (routes ~@body))) - -(defn- pre-init [middleware] - (let [proxy (middleware - (fn - ([request] - ((:route-handler request) request)) - ([request respond raise] - ((:route-handler request) request respond raise))))] - (fn [handler] - (let [prep-request #(assoc % :route-handler handler)] - (fn - ([request] - (proxy (prep-request request))) - ([request respond raise] - (proxy (prep-request request) respond raise))))))) - -(defn wrap-routes - "Apply a middleware function to routes after they have been matched." - ([handler middleware] - (let [middleware (pre-init middleware) - prep-request (fn [request] - (let [mw (:route-middleware request identity)] - (assoc request :route-middleware (comp mw middleware))))] - (fn - ([request] - (handler (prep-request request))) - ([request respond raise] - (handler (prep-request request) respond raise))))) - ([handler middleware & args] - (wrap-routes handler #(apply middleware % args)))) + (let [greq (gensym "greq")] + `(fn [~greq] + ~(macroexpand-1 `(let-request [~bindings ~greq] ~@body))))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj index 245cf85a..7b95c17b 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj @@ -1,71 +1,7 @@ ;; Copyright © 2024 James Reeves ;; ;; Distributed under the Eclipse Public License, the same as Clojure. -(ns compojure-api-kondo-hooks.compojure.core - (:require [compojure.response :as response] - [clojure.tools.macro :as macro] - [clout.core :as clout] - [ring.middleware.head :refer [wrap-head]] - [ring.util.codec :as codec] - [medley.core :refer [map-vals]])) - -(defn- form-method [request] - (or (get-in request [:form-params "_method"]) - (get-in request [:multipart-params "_method"]))) - -(defn- method-matches? [request method] - (or (nil? method) - (let [request-method (:request-method request)] - (case request-method - :head - (or (= method :head) (= method :get)) - :post - (if-let [fm (form-method request)] - (.equalsIgnoreCase (name method) fm) - (= method :post)) - ;else - (= request-method method))))) - -(defn- head-response [response request method] - (if (and response (= :get method) (= :head (:request-method request))) - (assoc response :body nil) - response)) - -(defn- decode-route-params [params] - (map-vals codec/url-decode params)) - -(defn- assoc-route-params [request params] - (merge-with merge request {:route-params params, :params params})) - -(defn- route-matches [route request] - (let [path (:compojure/path request)] - (clout/route-matches route (cond-> request path (assoc :path-info path))))) - -(defn- route-request [request route] - (if-let [params (route-matches route request)] - (assoc-route-params request (decode-route-params params)))) - -(defn- literal? [x] - (if (coll? x) - (every? literal? x) - (not (or (symbol? x) (list? x))))) - -(defn- prepare-route [route] - (cond - (string? route) - (clout/route-compile route) - (and (vector? route) (literal? route)) - (clout/route-compile - (first route) - (apply hash-map (rest route))) - (vector? route) - `(clout/route-compile - ~(first route) - ~(apply hash-map (rest route))) - :else - `(if (string? ~route) - (clout/route-compile ~route) - ~route))) +(ns compojure-api-kondo-hooks.compojure.core) (defn- and-binding [req binds] `(dissoc (:params ~req) ~@(map keyword (keys binds)) ~@(map str (keys binds)))) @@ -108,7 +44,6 @@ (seq syms)))) (defmacro ^:no-doc let-request [[bindings request] & body] - (warn-on-*-bindings! bindings) (if (vector? bindings) `(let [~@(vector-bindings bindings request)] ~(if-let [syms (application-symbols bindings)] @@ -116,238 +51,10 @@ `(do ~@body))) `(let [~bindings ~request] ~@body))) -(defn- wrap-route-middleware [handler] - (fn - ([request] - (if-let [mw (:route-middleware request)] - ((mw handler) request) - (handler request))) - ([request respond raise] - (if-let [mw (:route-middleware request)] - ((mw handler) request respond raise) - (handler request respond raise))))) - -(defn- wrap-route-info [handler route-info] - (fn - ([request] - (handler (assoc request :compojure/route route-info))) - ([request respond raise] - (handler (assoc request :compojure/route route-info) respond raise)))) - -(defn- wrap-route-matches [handler method path] - (fn - ([request] - (if (method-matches? request method) - (if-let [request (route-request request path)] - (-> (handler request) - (head-response request method))))) - ([request respond raise] - (if (method-matches? request method) - (if-let [request (route-request request path)] - (handler request #(respond (head-response % request method)) raise) - (respond nil)) - (respond nil))))) - -(defn- wrap-response [handler] - (fn - ([request] - (response/render (handler request) request)) - ([request respond raise] - (response/send (handler request) request respond raise)))) - -(defn make-route - "Returns a function that will only call the handler if the method and path - match the request." - [method path handler] - (-> handler - (wrap-response) - (wrap-route-middleware) - (wrap-route-info [(or method :any) (str path)]) - (wrap-route-matches method path))) - (defn compile-route "Compile a route in the form `(method path bindings & body)` into a function. Used to create custom route macros." [method path bindings body] - `(make-route - ~method - ~(prepare-route path) - (fn [request#] - (let-request [~bindings request#] ~@body)))) - -(defn routing - "Apply a list of routes to a Ring request map." - [request & handlers] - (some #(% request) handlers)) - -(defn routes - "Create a Ring handler by combining several handlers into one." - [& handlers] - (fn - ([request] - (apply routing request handlers)) - ([request respond raise] - (letfn [(f [handlers] - (if (seq handlers) - (let [handler (first handlers) - respond' #(if % (respond %) (f (rest handlers)))] - (handler request respond' raise)) - (respond nil)))] - (f handlers))))) - -(defmacro defroutes - "Define a Ring handler function from a sequence of routes. The name may - optionally be followed by a doc-string and metadata map." - [name & routes] - (let [[name routes] (macro/name-with-attributes name routes)] - `(def ~name (routes ~@routes)))) - -(defmacro GET "Generate a `GET` route." - [path args & body] - (compile-route :get path args body)) - -(defmacro POST "Generate a `POST` route." - [path args & body] - (compile-route :post path args body)) - -(defmacro PUT "Generate a `PUT` route." - [path args & body] - (compile-route :put path args body)) - -(defmacro DELETE "Generate a `DELETE` route." - [path args & body] - (compile-route :delete path args body)) - -(defmacro HEAD "Generate a `HEAD` route." - [path args & body] - (compile-route :head path args body)) - -(defmacro OPTIONS "Generate an `OPTIONS` route." - [path args & body] - (compile-route :options path args body)) - -(defmacro PATCH "Generate a `PATCH` route." - [path args & body] - (compile-route :patch path args body)) - -(defmacro ANY "Generate a route that matches any method." - [path args & body] - (compile-route nil path args body)) - -(defn ^:no-doc make-rfn [handler] - (-> handler - wrap-response - wrap-route-middleware - wrap-head)) - -(defmacro rfn "Generate a route that matches any method and path." - [args & body] - `(make-rfn (fn [request#] (let-request [~args request#] ~@body)))) - -(defn- remove-suffix [path suffix] - (subs path 0 (- (count path) (count suffix)))) - -(defn- context-request [request route route-context] - (if-let [params (clout/route-matches route request)] - (let [uri (:uri request) - path (:path-info request uri) - context (or (:context request) "") - subpath (:__path-info params) - params (dissoc params :__path-info)] - (-> request - (assoc-route-params (decode-route-params params)) - (assoc :path-info (if (= subpath "") "/" subpath) - :context (remove-suffix uri subpath)) - (update :compojure/route-context str route-context))))) - -(defn- context-route [route] - (let [re-context {:__path-info #"|/.*"}] - (cond - (string? route) - (clout/route-compile (str route ":__path-info") re-context) - (and (vector? route) (literal? route)) - (clout/route-compile - (str (first route) ":__path-info") - (merge (apply hash-map (rest route)) re-context)) - (vector? route) - `(clout/route-compile - (str ~(first route) ":__path-info") - ~(merge (apply hash-map (rest route)) re-context)) - :else - `(clout/route-compile (str ~route ":__path-info") ~re-context)))) - -(defn ^:no-doc make-context [route path make-handler] - (letfn [(handler - ([request] - (when-let [context-handler (make-handler request)] - (context-handler request))) - ([request respond raise] - (if-let [context-handler (make-handler request)] - (context-handler request respond raise) - (respond nil))))] - (if (#{":__path-info" "/:__path-info"} (:source route)) - handler - (fn - ([request] - (if-let [request (context-request request route path)] - (handler request))) - ([request respond raise] - (if-let [request (context-request request route path)] - (handler request respond raise) - (respond nil))))))) - -(defmacro context - "Give all routes in the form a common path prefix and set of bindings. - - The following example demonstrates defining two routes with a common - path prefix ('/user/:id') and a common binding ('id'): - - (context \"/user/:id\" [id] - (GET \"/profile\" [] ...) - (GET \"/settings\" [] ...))" - [path args & routes] - `(make-context - ~(context-route path) - ~path - (fn [request#] - (let-request [~args request#] - (routes ~@routes))))) - -(defmacro let-routes - "Takes a vector of bindings and a body of routes. - - Equivalent to: - - (let [...] (routes ...))" - [bindings & body] - `(let ~bindings (routes ~@body))) - -(defn- pre-init [middleware] - (let [proxy (middleware - (fn - ([request] - ((:route-handler request) request)) - ([request respond raise] - ((:route-handler request) request respond raise))))] - (fn [handler] - (let [prep-request #(assoc % :route-handler handler)] - (fn - ([request] - (proxy (prep-request request))) - ([request respond raise] - (proxy (prep-request request) respond raise))))))) - -(defn wrap-routes - "Apply a middleware function to routes after they have been matched." - ([handler middleware] - (let [middleware (pre-init middleware) - prep-request (fn [request] - (let [mw (:route-middleware request identity)] - (assoc request :route-middleware (comp mw middleware))))] - (fn - ([request] - (handler (prep-request request))) - ([request respond raise] - (handler (prep-request request) respond raise))))) - ([handler middleware & args] - (wrap-routes handler #(apply middleware % args)))) + (let [greq (gensym "greq")] + `(fn [~greq] + ~(macroexpand-1 `(let-request [~bindings ~greq] ~@body))))) From 7a8ef4b3e25691c4b75184ae39b05bac53b5a223 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 16:55:35 -0500 Subject: [PATCH 29/39] green --- README.md | 8 ++++++++ .../imports/metosin/compojure-api/compojure/api/meta.clj | 1 - examples/clj-kondo-hooks/output/expected-output | 0 .../metosin/compojure-api/compojure/api/meta.clj | 1 - src/compojure/api/meta.cljc | 1 - 5 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 examples/clj-kondo-hooks/output/expected-output diff --git a/README.md b/README.md index e1b70816..f6f9f2af 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,14 @@ which can be found in the file epl-v10.html at the root of this distribution. By be bound bythe terms of this license. You must not remove this notice, or any other, from this software. ``` +Copied code from compojure has license: + +``` +Copyright © 2024 James Reeves + +Distributed under the Eclipse Public License, the same as Clojure. +``` + All other code: Copyright © 2014-2016 [Metosin Oy](https://www.metosin.fi) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index 3d79a53b..e0ff2bc4 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -398,7 +398,6 @@ form `(fn [~'+compojure-api-request+] ~'+compojure-api-request+ ;;always used ~form)] - (prn "form" form) form))) :default ;; JVM (if context? diff --git a/examples/clj-kondo-hooks/output/expected-output b/examples/clj-kondo-hooks/output/expected-output new file mode 100644 index 00000000..e69de29b diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index 3d79a53b..e0ff2bc4 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -398,7 +398,6 @@ form `(fn [~'+compojure-api-request+] ~'+compojure-api-request+ ;;always used ~form)] - (prn "form" form) form))) :default ;; JVM (if context? diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index bab14e67..16370800 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -398,7 +398,6 @@ form `(fn [~'+compojure-api-request+] ~'+compojure-api-request+ ;;always used ~form)] - (prn "form" form) form))) :default ;; JVM (if context? From dc9121f83e17830f241af9a92bedd4a800398c63 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 16:56:40 -0500 Subject: [PATCH 30/39] test --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd565b5e..43614722 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,6 +42,8 @@ jobs: uses: DeLaGuardo/setup-clojure@master with: lein: latest + - name: Check kondo hooks + run: cd examples/clj-kondo-hooks && ./script/test - name: Run tests run: lein do clean, all midje, all check deploy: From afe10e1f135b1aa4a41def4ed39728a5210ae5fa Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 17:03:17 -0500 Subject: [PATCH 31/39] wip --- .../imports/metosin/compojure-api/config.edn | 2 +- examples/clj-kondo-hooks/output/expected-output | 1 + .../src/compojure_api_example/clj_kondo_hooks.clj | 10 +++++++--- .../clj-kondo.exports/metosin/compojure-api/config.edn | 2 +- scripts/regen_kondo_config.clj | 7 ++++--- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn index 2a6b3d4b..dcc7e15f 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn @@ -1 +1 @@ -{:linters {:unresolved-namespace {:exclude [(compojure.api.routes)]}}, :hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}} \ No newline at end of file +{:hooks {:macroexpand {}}} \ No newline at end of file diff --git a/examples/clj-kondo-hooks/output/expected-output b/examples/clj-kondo-hooks/output/expected-output index e69de29b..c380aebd 100644 --- a/examples/clj-kondo-hooks/output/expected-output +++ b/examples/clj-kondo-hooks/output/expected-output @@ -0,0 +1 @@ +src/compojure_api_example/clj_kondo_hooks.clj:7:20: error: keyword :ok is called with 0 args but expects 1 or 2 diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index fd6cf38a..0e125c6b 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -4,7 +4,11 @@ [ring.util.http-response :as resp])) (core/GET "/30" [] (resp/ok {:result 30})) +(core/GET "/30" [] (:ok)) ;; src/compojure_api_example/clj_kondo_hooks.clj:7:20: error: keyword :ok is called with 0 args but expects 1 or 2 +(core/GET "/30" [] '(:ok)) -(core/GET "/30" req - (do +compojure-api-request+ - (resp/ok {:result (:body req)}))) +(core/GET "/30" req (resp/ok {:result (:body req)})) +(core/GET "/30" [:as req] (resp/ok {:result (:body req)})) +(core/GET "/30" {:keys [body]} (resp/ok {:result body})) +(core/GET "/30" {:as req} (resp/ok {:result (:body req)})) +(core/GET "/30" {:as req} (resp/ok {:result (:body req)})) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/config.edn b/resources/clj-kondo.exports/metosin/compojure-api/config.edn index 2a6b3d4b..dcc7e15f 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/config.edn +++ b/resources/clj-kondo.exports/metosin/compojure-api/config.edn @@ -1 +1 @@ -{:linters {:unresolved-namespace {:exclude [(compojure.api.routes)]}}, :hooks {:macroexpand {compojure.api.core/GET compojure.api.core/GET}}} \ No newline at end of file +{:hooks {:macroexpand {}}} \ No newline at end of file diff --git a/scripts/regen_kondo_config.clj b/scripts/regen_kondo_config.clj index 880fd496..316e0208 100755 --- a/scripts/regen_kondo_config.clj +++ b/scripts/regen_kondo_config.clj @@ -10,16 +10,17 @@ "src/compojure/api/meta.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj" "dev/compojure_api_kondo_hooks/compojure/core.clj" "resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj"}) +(def restructured-macro-names + '#{context GET ANY HEAD PATCH DELETE OPTIONS POST PUT}) (defn -main [& args] (doseq [[from to] renames] (spit to (str/replace (slurp from) ":clj-kondo" ":default"))) (spit "resources/clj-kondo.exports/metosin/compojure-api/config.edn" - '{:linters {:unresolved-namespace {:exclude [(compojure.api.routes)]}} - :hooks + '{:hooks {:macroexpand - {compojure.api.core/GET compojure.api.core/GET + {;compojure.api.core/GET compojure.api.core/GET }}})) (when (= *file* (System/getProperty "babashka.file")) (-main)) From 4a45d206972d5779f1ee4faf69fa3a4f68e3cd57 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 17:06:55 -0500 Subject: [PATCH 32/39] regen --- .../imports/metosin/compojure-api/config.edn | 2 +- .../src/compojure_api_example/clj_kondo_hooks.clj | 2 ++ .../metosin/compojure-api/config.edn | 2 +- scripts/regen_kondo_config.clj | 14 ++++++++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn index dcc7e15f..d0cb4934 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/config.edn @@ -1 +1 @@ -{:hooks {:macroexpand {}}} \ No newline at end of file +{:hooks {:macroexpand {compojure.api.sweet/PATCH compojure.api.core/PATCH, compojure.api.sweet/ANY compojure.api.core/ANY, compojure.api.sweet/context compojure.api.core/context, compojure.api.core/POST compojure.api.core/POST, compojure.api.core/GET compojure.api.core/GET, compojure.api.sweet/DELETE compojure.api.core/DELETE, compojure.api.core/ANY compojure.api.core/ANY, compojure.api.sweet/POST compojure.api.core/POST, compojure.api.sweet/GET compojure.api.core/GET, compojure.api.sweet/HEAD compojure.api.core/HEAD, compojure.api.core/PUT compojure.api.core/PUT, compojure.api.core/DELETE compojure.api.core/DELETE, compojure.api.sweet/OPTIONS compojure.api.core/OPTIONS, compojure.api.sweet/PUT compojure.api.core/PUT, compojure.api.core/context compojure.api.core/context, compojure.api.core/HEAD compojure.api.core/HEAD, compojure.api.core/PATCH compojure.api.core/PATCH, compojure.api.core/OPTIONS compojure.api.core/OPTIONS}}} \ No newline at end of file diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index 0e125c6b..698587fd 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -12,3 +12,5 @@ (core/GET "/30" {:keys [body]} (resp/ok {:result body})) (core/GET "/30" {:as req} (resp/ok {:result (:body req)})) (core/GET "/30" {:as req} (resp/ok {:result (:body req)})) + +(core/PUT "/30" req (resp/ok {:result (:body req)})) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/config.edn b/resources/clj-kondo.exports/metosin/compojure-api/config.edn index dcc7e15f..d0cb4934 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/config.edn +++ b/resources/clj-kondo.exports/metosin/compojure-api/config.edn @@ -1 +1 @@ -{:hooks {:macroexpand {}}} \ No newline at end of file +{:hooks {:macroexpand {compojure.api.sweet/PATCH compojure.api.core/PATCH, compojure.api.sweet/ANY compojure.api.core/ANY, compojure.api.sweet/context compojure.api.core/context, compojure.api.core/POST compojure.api.core/POST, compojure.api.core/GET compojure.api.core/GET, compojure.api.sweet/DELETE compojure.api.core/DELETE, compojure.api.core/ANY compojure.api.core/ANY, compojure.api.sweet/POST compojure.api.core/POST, compojure.api.sweet/GET compojure.api.core/GET, compojure.api.sweet/HEAD compojure.api.core/HEAD, compojure.api.core/PUT compojure.api.core/PUT, compojure.api.core/DELETE compojure.api.core/DELETE, compojure.api.sweet/OPTIONS compojure.api.core/OPTIONS, compojure.api.sweet/PUT compojure.api.core/PUT, compojure.api.core/context compojure.api.core/context, compojure.api.core/HEAD compojure.api.core/HEAD, compojure.api.core/PATCH compojure.api.core/PATCH, compojure.api.core/OPTIONS compojure.api.core/OPTIONS}}} \ No newline at end of file diff --git a/scripts/regen_kondo_config.clj b/scripts/regen_kondo_config.clj index 316e0208..b89a4e88 100755 --- a/scripts/regen_kondo_config.clj +++ b/scripts/regen_kondo_config.clj @@ -18,9 +18,15 @@ (spit to (str/replace (slurp from) ":clj-kondo" ":default"))) (spit "resources/clj-kondo.exports/metosin/compojure-api/config.edn" - '{:hooks - {:macroexpand - {;compojure.api.core/GET compojure.api.core/GET - }}})) + {:hooks + {:macroexpand + (reduce + (fn [m n] + (let [core-macro (symbol "compojure.api.core" (name n)) + sweet-macro (symbol "compojure.api.sweet" (name n))] + (-> m + (assoc core-macro core-macro + sweet-macro core-macro)))) + {} restructured-macro-names)}})) (when (= *file* (System/getProperty "babashka.file")) (-main)) From 6bb6bb869cf07e42df473a9f132df30f38013244 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 17:09:54 -0500 Subject: [PATCH 33/39] wip --- .../clj-kondo-hooks/output/expected-output | 3 ++- .../compojure_api_example/clj_kondo_hooks.clj | 27 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/examples/clj-kondo-hooks/output/expected-output b/examples/clj-kondo-hooks/output/expected-output index c380aebd..264102e4 100644 --- a/examples/clj-kondo-hooks/output/expected-output +++ b/examples/clj-kondo-hooks/output/expected-output @@ -1 +1,2 @@ -src/compojure_api_example/clj_kondo_hooks.clj:7:20: error: keyword :ok is called with 0 args but expects 1 or 2 +src/compojure_api_example/clj_kondo_hooks.clj:26:20: error: keyword :ok is called with 0 args but expects 1 or 2 +src/compojure_api_example/clj_kondo_hooks.clj:30:21: error: keyword :ok is called with 0 args but expects 1 or 2 diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index 698587fd..a1a7733d 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -1,12 +1,35 @@ (ns compojure-api-example.clj-kondo-hooks - (:require ;[compojure.api.sweet :as sweet] + (:require [compojure.api.sweet :as sweet] [compojure.api.core :as core] - [ring.util.http-response :as resp])) + [ring.util.http-response :as resp] + ;; intentionally blank + ;; intentionally blank + ;; intentionally blank + ;; intentionally blank + ;; intentionally blank + ;; intentionally blank + ;; intentionally blank + ;; intentionally blank + )) +;; intentionally blank +;; intentionally blank +;; intentionally blank +;; intentionally blank +;; intentionally blank +;; intentionally blank +;; intentionally blank +;; intentionally blank +;; intentionally blank +;; intentionally blank (core/GET "/30" [] (resp/ok {:result 30})) (core/GET "/30" [] (:ok)) ;; src/compojure_api_example/clj_kondo_hooks.clj:7:20: error: keyword :ok is called with 0 args but expects 1 or 2 (core/GET "/30" [] '(:ok)) +(sweet/GET "/30" [] (resp/ok {:result 30})) +(sweet/GET "/30" [] (:ok)) ;; src/compojure_api_example/clj_kondo_hooks.clj:7:20: error: keyword :ok is called with 0 args but expects 1 or 2 +(sweet/GET "/30" [] '(:ok)) + (core/GET "/30" req (resp/ok {:result (:body req)})) (core/GET "/30" [:as req] (resp/ok {:result (:body req)})) (core/GET "/30" {:keys [body]} (resp/ok {:result body})) From a9d977989075dd70d5dc199fef90878b9c16babd Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 17:10:34 -0500 Subject: [PATCH 34/39] wip --- .../src/compojure_api_example/clj_kondo_hooks.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index a1a7733d..9de0fe5d 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -23,11 +23,11 @@ ;; intentionally blank (core/GET "/30" [] (resp/ok {:result 30})) -(core/GET "/30" [] (:ok)) ;; src/compojure_api_example/clj_kondo_hooks.clj:7:20: error: keyword :ok is called with 0 args but expects 1 or 2 +(core/GET "/30" [] (:ok)) ;; src/compojure_api_example/clj_kondo_hooks.clj:26:20: error: keyword :ok is called with 0 args but expects 1 or 2 (core/GET "/30" [] '(:ok)) (sweet/GET "/30" [] (resp/ok {:result 30})) -(sweet/GET "/30" [] (:ok)) ;; src/compojure_api_example/clj_kondo_hooks.clj:7:20: error: keyword :ok is called with 0 args but expects 1 or 2 +(sweet/GET "/30" [] (:ok)) ;; src/compojure_api_example/clj_kondo_hooks.clj:30:21: error: keyword :ok is called with 0 args but expects 1 or 2 (sweet/GET "/30" [] '(:ok)) (core/GET "/30" req (resp/ok {:result (:body req)})) From c6b0a9765963bc284e9495ea4ce5726e03bfdbe1 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 17:16:15 -0500 Subject: [PATCH 35/39] wip --- examples/clj-kondo-hooks/output/expected-output | 1 + .../src/compojure_api_example/clj_kondo_hooks.clj | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/examples/clj-kondo-hooks/output/expected-output b/examples/clj-kondo-hooks/output/expected-output index 264102e4..a1a7842c 100644 --- a/examples/clj-kondo-hooks/output/expected-output +++ b/examples/clj-kondo-hooks/output/expected-output @@ -1,2 +1,3 @@ src/compojure_api_example/clj_kondo_hooks.clj:26:20: error: keyword :ok is called with 0 args but expects 1 or 2 src/compojure_api_example/clj_kondo_hooks.clj:30:21: error: keyword :ok is called with 0 args but expects 1 or 2 +src/compojure_api_example/clj_kondo_hooks.clj:38:44: error: Unresolved symbol: req diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index 9de0fe5d..9637129d 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -2,8 +2,8 @@ (:require [compojure.api.sweet :as sweet] [compojure.api.core :as core] [ring.util.http-response :as resp] - ;; intentionally blank - ;; intentionally blank + [schema.core :as s] + [ring.util.http-response :refer [describe]] ;; intentionally blank ;; intentionally blank ;; intentionally blank @@ -35,5 +35,15 @@ (core/GET "/30" {:keys [body]} (resp/ok {:result body})) (core/GET "/30" {:as req} (resp/ok {:result (:body req)})) (core/GET "/30" {:as req} (resp/ok {:result (:body req)})) +(core/GET "/30" _ (resp/ok {:result (:body req)})) ;; src/compojure_api_example/clj_kondo_hooks.clj:38:44: error: Unresolved symbol: req (core/PUT "/30" req (resp/ok {:result (:body req)})) + +(core/routes + (core/PUT "/" [] + :responses {200 {:schema s/Any}} + :summary "summary" + :query-params [{qparam :- s/Int nil}] + :body [body (describe s/Any "description")] + :description (str "foo" "bar") + (ok (str qparam body)))) From 3654819d946d16fdb43fb8c3523a691ad8043322 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 17:17:49 -0500 Subject: [PATCH 36/39] [skip ci] --- .../src/compojure_api_example/clj_kondo_hooks.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj index 9637129d..1e6b62d0 100644 --- a/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj +++ b/examples/clj-kondo-hooks/src/compojure_api_example/clj_kondo_hooks.clj @@ -3,7 +3,7 @@ [compojure.api.core :as core] [ring.util.http-response :as resp] [schema.core :as s] - [ring.util.http-response :refer [describe]] + ;; intentionally blank ;; intentionally blank ;; intentionally blank ;; intentionally blank @@ -44,6 +44,6 @@ :responses {200 {:schema s/Any}} :summary "summary" :query-params [{qparam :- s/Int nil}] - :body [body (describe s/Any "description")] + :body [body (resp/describe s/Any "description")] :description (str "foo" "bar") - (ok (str qparam body)))) + (resp/ok (str qparam body)))) From 0d4a12cf1a32a88c77a7949f11945ea710498fd8 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 17:19:28 -0500 Subject: [PATCH 37/39] wip --- .../compojure-api/compojure/api/common.clj | 4 +- .../compojure-api/compojure/api/core.clj | 32 +++++++------- .../compojure-api/compojure/api/meta.clj | 42 +++++++++---------- .../compojure-api/compojure/api/common.clj | 4 +- .../compojure-api/compojure/api/core.clj | 32 +++++++------- .../compojure-api/compojure/api/meta.clj | 42 +++++++++---------- scripts/regen_kondo_config.clj | 2 +- 7 files changed, 79 insertions(+), 79 deletions(-) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj index 78a21197..2a0dc7d5 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/common.clj @@ -1,5 +1,5 @@ (ns compojure.api.common - #?@(:default [] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [(:require [linked.core :as linked])])) (defn plain-map? @@ -52,7 +52,7 @@ x y)) -#?(:default nil +#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (defn fifo-memoize [f size] "Returns a memoized version of a referentially transparent f. The diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj index 772a0ed2..ffb711ea 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/core.clj @@ -1,10 +1,10 @@ (ns compojure.api.core (:require [compojure.api.meta :as meta] - #?@(:default [] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [[compojure.api.async] [compojure.core :as compojure]]) - [compojure.api.routes #?(:default :as-alias :default :as) routes] - [compojure.api.middleware #?(:default :as-alias :default :as) mw])) + [compojure.api.routes #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) routes] + [compojure.api.middleware #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) mw])) (defn ring-handler "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" @@ -16,7 +16,7 @@ (defn routes "Create a Ring handler by combining several handlers into one." [& handlers] - #?(:default (throw (ex-info "Not supported in bb")) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" (throw (ex-info "Not supported in bb")) :default (let [handlers (seq (keep identity (flatten handlers)))] (routes/map->Route {:childs (vec handlers) @@ -42,7 +42,7 @@ "Routes without route-documentation. Can be used to wrap routes, not satisfying compojure.api.routes/Routing -protocol." [& handlers] - #?(:default (throw (ex-info "Not supported in bb")) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" (throw (ex-info "Not supported in bb")) :default (let [handlers (keep identity handlers)] (routes/map->Route {:handler (meta/routing handlers)})))) @@ -56,7 +56,7 @@ :deprecated "1.1.14" :superseded-by "route-middleware"} [middleware & body] - #?(:default nil + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) (println (str "compojure.api.core.middleware is deprecated because of security issues. " "Please use route-middleware instead. middleware will be disabled in a future release." @@ -70,7 +70,7 @@ {:style/indent 1 :supercedes "middleware"} [middleware & body] - #?(:default (throw (ex-info "Not supported in bb")) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" (throw (ex-info "Not supported in bb")) :default (let [handler (apply routes body) x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] @@ -79,13 +79,13 @@ {:childs [handler] :handler x-handler})))) -(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:default true :default false)})) +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" true :default false)})) -(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:default {:kondo-rule? true} :default nil))) -(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:default {:kondo-rule? true} :default nil))) -(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:default {:kondo-rule? true} :default nil))) -(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:default {:kondo-rule? true} :default nil))) -(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:default {:kondo-rule? true} :default nil))) -(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:default {:kondo-rule? true} :default nil))) -(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:default {:kondo-rule? true} :default nil))) -(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:default {:kondo-rule? true} :default nil))) +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index e0ff2bc4..f2553622 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -1,22 +1,22 @@ (ns compojure.api.meta (:require [clojure.walk :as walk] [compojure.api.common :as common :refer [extract-parameters]] - [compojure.api.middleware #?(:default :as-alias :default :as) mw] - [compojure.api.routes #?(:default :as-alias :default :as) routes] - [plumbing.core #?(:default :as-alias :default :as) p] - [plumbing.fnk.impl #?(:default :as-alias :default :as) fnk-impl] - [ring.swagger.common #?(:default :as-alias :default :as) rsc] - [ring.swagger.json-schema #?(:default :as-alias :default :as) js] - #?@(:default [] + [compojure.api.middleware #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) mw] + [compojure.api.routes #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) routes] + [plumbing.core #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) p] + [plumbing.fnk.impl #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) fnk-impl] + [ring.swagger.common #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) rsc] + [ring.swagger.json-schema #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) js] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [[schema.core :as s] [schema-tools.core :as st]]) - [compojure.api.coerce #?(:default :as-alias :default :as) coerce] - #?(:default [compojure-api-kondo-hooks.compojure.core :as comp-core] + [compojure.api.coerce #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) coerce] + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [compojure-api-kondo-hooks.compojure.core :as comp-core] :default [compojure.core :as comp-core]))) (defmacro ^:private system-property-check [& body] - #?(:default nil + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default `(do ~@body))) (def +compojure-api-request+ @@ -61,7 +61,7 @@ (dissoc schema 'schema.core/Keyword)) (defn fnk-schema [bind] - #?(:default {} + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {} :default (->> (:input-schema (fnk-impl/letk-input-schema-and-body-form @@ -75,12 +75,12 @@ [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) (assert (#{:body :string :response} type)) - #?(:default `(do ~schema ~key ~type ~+compojure-api-request+) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" `(do ~schema ~key ~type ~+compojure-api-request+) :default `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+))) (defn- convert-return [schema] {200 {:schema schema - :description (or #?(:default nil + :description (or #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (js/json-schema-meta schema)) "")}}) @@ -223,7 +223,7 @@ (let [schema (strict (fnk-schema form-params))] (-> acc (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) - #?@(:default [] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) @@ -233,7 +233,7 @@ (let [schema (strict (fnk-schema params))] (-> acc (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) - #?@(:default [] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["multipart/form-data"])))) @@ -271,7 +271,7 @@ ; route-specific override for coercers (defmethod restructure-param :coercion [_ coercion acc] - (update-in acc [:middleware] conj [#?(:default `mw/wrap-coercion + (update-in acc [:middleware] conj [#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" `mw/wrap-coercion ;;FIXME why not quoted? :default mw/wrap-coercion) coercion])) @@ -280,7 +280,7 @@ ;; Impl ;; -#?(:default nil +#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (defmacro dummy-let "Dummy let-macro used in resolving route-docs. not part of normal invocation chain." @@ -289,7 +289,7 @@ `(let ~bind-form ~@body))) ) -#?(:default nil +#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (defmacro dummy-letk "Dummy letk-macro used in resolving route-docs. not part of normal invocation chain." @@ -309,7 +309,7 @@ (reverse (partition 2 bindings)))) ) -#?(:default nil +#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (defn routing [handlers] (if-let [handlers (seq (keep identity (flatten handlers)))] @@ -346,7 +346,7 @@ (defn merge-parameters "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." [{:keys [responses swagger] :as parameters}] - #?(:default parameters + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" parameters :default (cond-> parameters (seq responses) (assoc :responses (common/merge-vector responses)) swagger (-> (dissoc :swagger) (rsc/deep-merge swagger))))) @@ -381,7 +381,7 @@ ;; response coercion middleware, why not just code? middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] - #?(:default (do (assert kondo-rule?) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" (do (assert kondo-rule?) (if context? ;; context (let [form `(do ~@body) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj index 78a21197..2a0dc7d5 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj @@ -1,5 +1,5 @@ (ns compojure.api.common - #?@(:default [] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [(:require [linked.core :as linked])])) (defn plain-map? @@ -52,7 +52,7 @@ x y)) -#?(:default nil +#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (defn fifo-memoize [f size] "Returns a memoized version of a referentially transparent f. The diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj index 772a0ed2..ffb711ea 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj @@ -1,10 +1,10 @@ (ns compojure.api.core (:require [compojure.api.meta :as meta] - #?@(:default [] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [[compojure.api.async] [compojure.core :as compojure]]) - [compojure.api.routes #?(:default :as-alias :default :as) routes] - [compojure.api.middleware #?(:default :as-alias :default :as) mw])) + [compojure.api.routes #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) routes] + [compojure.api.middleware #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) mw])) (defn ring-handler "Creates vanilla ring-handler from any invokable thing (e.g. compojure-api route)" @@ -16,7 +16,7 @@ (defn routes "Create a Ring handler by combining several handlers into one." [& handlers] - #?(:default (throw (ex-info "Not supported in bb")) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" (throw (ex-info "Not supported in bb")) :default (let [handlers (seq (keep identity (flatten handlers)))] (routes/map->Route {:childs (vec handlers) @@ -42,7 +42,7 @@ "Routes without route-documentation. Can be used to wrap routes, not satisfying compojure.api.routes/Routing -protocol." [& handlers] - #?(:default (throw (ex-info "Not supported in bb")) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" (throw (ex-info "Not supported in bb")) :default (let [handlers (keep identity handlers)] (routes/map->Route {:handler (meta/routing handlers)})))) @@ -56,7 +56,7 @@ :deprecated "1.1.14" :superseded-by "route-middleware"} [middleware & body] - #?(:default nil + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning")) (println (str "compojure.api.core.middleware is deprecated because of security issues. " "Please use route-middleware instead. middleware will be disabled in a future release." @@ -70,7 +70,7 @@ {:style/indent 1 :supercedes "middleware"} [middleware & body] - #?(:default (throw (ex-info "Not supported in bb")) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" (throw (ex-info "Not supported in bb")) :default (let [handler (apply routes body) x-handler (compojure/wrap-routes handler (mw/compose-middleware middleware))] @@ -79,13 +79,13 @@ {:childs [handler] :handler x-handler})))) -(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:default true :default false)})) +(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env :kondo-rule? #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" true :default false)})) -(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:default {:kondo-rule? true} :default nil))) -(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:default {:kondo-rule? true} :default nil))) -(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:default {:kondo-rule? true} :default nil))) -(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:default {:kondo-rule? true} :default nil))) -(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:default {:kondo-rule? true} :default nil))) -(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:default {:kondo-rule? true} :default nil))) -(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:default {:kondo-rule? true} :default nil))) -(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:default {:kondo-rule? true} :default nil))) +(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) +(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {:kondo-rule? true} :default nil))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index e0ff2bc4..f2553622 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -1,22 +1,22 @@ (ns compojure.api.meta (:require [clojure.walk :as walk] [compojure.api.common :as common :refer [extract-parameters]] - [compojure.api.middleware #?(:default :as-alias :default :as) mw] - [compojure.api.routes #?(:default :as-alias :default :as) routes] - [plumbing.core #?(:default :as-alias :default :as) p] - [plumbing.fnk.impl #?(:default :as-alias :default :as) fnk-impl] - [ring.swagger.common #?(:default :as-alias :default :as) rsc] - [ring.swagger.json-schema #?(:default :as-alias :default :as) js] - #?@(:default [] + [compojure.api.middleware #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) mw] + [compojure.api.routes #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) routes] + [plumbing.core #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) p] + [plumbing.fnk.impl #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) fnk-impl] + [ring.swagger.common #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) rsc] + [ring.swagger.json-schema #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) js] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [[schema.core :as s] [schema-tools.core :as st]]) - [compojure.api.coerce #?(:default :as-alias :default :as) coerce] - #?(:default [compojure-api-kondo-hooks.compojure.core :as comp-core] + [compojure.api.coerce #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) coerce] + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [compojure-api-kondo-hooks.compojure.core :as comp-core] :default [compojure.core :as comp-core]))) (defmacro ^:private system-property-check [& body] - #?(:default nil + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default `(do ~@body))) (def +compojure-api-request+ @@ -61,7 +61,7 @@ (dissoc schema 'schema.core/Keyword)) (defn fnk-schema [bind] - #?(:default {} + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" {} :default (->> (:input-schema (fnk-impl/letk-input-schema-and-body-form @@ -75,12 +75,12 @@ [schema, key, type #_#_:- mw/CoercionType] (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) (assert (#{:body :string :response} type)) - #?(:default `(do ~schema ~key ~type ~+compojure-api-request+) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" `(do ~schema ~key ~type ~+compojure-api-request+) :default `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+))) (defn- convert-return [schema] {200 {:schema schema - :description (or #?(:default nil + :description (or #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (js/json-schema-meta schema)) "")}}) @@ -223,7 +223,7 @@ (let [schema (strict (fnk-schema form-params))] (-> acc (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)]) - #?@(:default [] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"])))) @@ -233,7 +233,7 @@ (let [schema (strict (fnk-schema params))] (-> acc (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)]) - #?@(:default [] + #?@(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [] :default [(update-in [:swagger :parameters :formData] st/merge schema)]) (assoc-in [:swagger :consumes] ["multipart/form-data"])))) @@ -271,7 +271,7 @@ ; route-specific override for coercers (defmethod restructure-param :coercion [_ coercion acc] - (update-in acc [:middleware] conj [#?(:default `mw/wrap-coercion + (update-in acc [:middleware] conj [#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" `mw/wrap-coercion ;;FIXME why not quoted? :default mw/wrap-coercion) coercion])) @@ -280,7 +280,7 @@ ;; Impl ;; -#?(:default nil +#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (defmacro dummy-let "Dummy let-macro used in resolving route-docs. not part of normal invocation chain." @@ -289,7 +289,7 @@ `(let ~bind-form ~@body))) ) -#?(:default nil +#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (defmacro dummy-letk "Dummy letk-macro used in resolving route-docs. not part of normal invocation chain." @@ -309,7 +309,7 @@ (reverse (partition 2 bindings)))) ) -#?(:default nil +#?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" nil :default (defn routing [handlers] (if-let [handlers (seq (keep identity (flatten handlers)))] @@ -346,7 +346,7 @@ (defn merge-parameters "Merge parameters at runtime to allow usage of runtime-parameters with route-macros." [{:keys [responses swagger] :as parameters}] - #?(:default parameters + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" parameters :default (cond-> parameters (seq responses) (assoc :responses (common/merge-vector responses)) swagger (-> (dissoc :swagger) (rsc/deep-merge swagger))))) @@ -381,7 +381,7 @@ ;; response coercion middleware, why not just code? middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] - #?(:default (do (assert kondo-rule?) + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" (do (assert kondo-rule?) (if context? ;; context (let [form `(do ~@body) diff --git a/scripts/regen_kondo_config.clj b/scripts/regen_kondo_config.clj index b89a4e88..8678848c 100755 --- a/scripts/regen_kondo_config.clj +++ b/scripts/regen_kondo_config.clj @@ -16,7 +16,7 @@ (defn -main [& args] (doseq [[from to] renames] (spit to - (str/replace (slurp from) ":clj-kondo" ":default"))) + (str/replace (slurp from) ":clj-kondo" ":default #_\"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj\""))) (spit "resources/clj-kondo.exports/metosin/compojure-api/config.edn" {:hooks {:macroexpand From c9819f7677053357c06bc6b1226e35a10ac4176d Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 18:00:40 -0500 Subject: [PATCH 38/39] wip --- .../plumbing/core.clj | 46 +++++++ .../plumbing/fnk/impl.clj | 116 ++++++++++++++++++ .../plumbing/fnk/schema.clj | 16 +++ .../schema/macros.clj | 24 ++++ .../compojure-api/compojure/api/meta.clj | 5 +- .../clj-kondo-hooks/output/expected-output | 5 +- .../compojure-api/compojure/api/meta.clj | 5 +- src/compojure/api/meta.cljc | 5 +- 8 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 dev/compojure_api_kondo_hooks/plumbing/core.clj create mode 100644 dev/compojure_api_kondo_hooks/plumbing/fnk/impl.clj create mode 100644 dev/compojure_api_kondo_hooks/plumbing/fnk/schema.clj create mode 100644 dev/compojure_api_kondo_hooks/schema/macros.clj diff --git a/dev/compojure_api_kondo_hooks/plumbing/core.clj b/dev/compojure_api_kondo_hooks/plumbing/core.clj new file mode 100644 index 00000000..5b6d1965 --- /dev/null +++ b/dev/compojure_api_kondo_hooks/plumbing/core.clj @@ -0,0 +1,46 @@ +(ns compojure-api-kondo-hooks.plumbing.core + "Utility belt for Clojure in the wild" + (:refer-clojure :exclude [update]) + (:require + [schema.utils :as schema-utils] + [schema.macros :as schema-macros] + [plumbing.fnk.schema :as schema] + [compojure-api-kondo-hooks.plumbing.fnk.impl :as fnk-impl])) + +(defmacro letk + "Keyword let. Accepts an interleaved sequence of binding forms and map forms like: + (letk [[a {b 2} [:f g h] c d {e 4} :as m & more] a-map ...] & body) + a, c, d, and f are required keywords, and letk will barf if not in a-map. + b and e are optional, and will be bound to default values if not present. + g and h are required keys in the map found under :f. + m will be bound to the entire map (a-map). + more will be bound to all the unbound keys (ie (dissoc a-map :a :b :c :d :e)). + :as and & are both optional, but must be at the end in the specified order if present. + The same symbol cannot be bound multiple times within the same destructing level. + + Optional values can reference symbols bound earlier within the same binding, i.e., + (= [2 2] (let [a 1] (letk [[a {b a}] {:a 2}] [a b]))) but + (= [2 1] (let [a 1] (letk [[{b a} a] {:a 2}] [a b]))) + + If present, :as and :& symbols are bound before other symbols within the binding. + + Namespaced keys are supported by specifying fully-qualified key in binding form. The bound + symbol uses the _name_ portion of the namespaced key, i.e, + (= 1 (letk [[a/b] {:a/b 1}] b)). + + Map destructuring bindings can be mixed with ordinary symbol bindings." + [bindings & body] + (schema/assert-iae (vector? bindings) "Letk binding must be a vector") + (schema/assert-iae (even? (count bindings)) "Letk binding must have even number of elements") + (reduce + (fn [cur-body-form [bind-form value-form]] + (if (symbol? bind-form) + `(let [~bind-form ~value-form] ~cur-body-form) + (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form + &env + bind-form ;(fnk-impl/ensure-schema-metadata &env bind-form) + [] + cur-body-form)] + `(let [~map-sym ~value-form] ~body-form)))) + `(do ~@body) + (reverse (partition 2 bindings)))) diff --git a/dev/compojure_api_kondo_hooks/plumbing/fnk/impl.clj b/dev/compojure_api_kondo_hooks/plumbing/fnk/impl.clj new file mode 100644 index 00000000..5e1ad75f --- /dev/null +++ b/dev/compojure_api_kondo_hooks/plumbing/fnk/impl.clj @@ -0,0 +1,116 @@ +(ns compojure-api-kondo-hooks.plumbing.fnk.impl + (:require + [clojure.set :as set] + [schema.core :as-alias s] + [compojure-api-kondo-hooks.schema.macros :as schema-macros])) + +;;;;; Helpers + +(defn name-sym + "Returns symbol of x's name. + Converts a keyword/string to symbol, or removes namespace (if any) of symbol" + [x] + (with-meta (symbol (name x)) (meta x))) + +;;; Parsing new fnk binding style + +(declare letk-input-schema-and-body-form) + +(defn- process-schematized-map + "Take an optional binding map like {a 2} or {a :- Number 2} and convert the schema + information to canonical metadata, if present." + [env binding] + (case (count binding) + 1 (let [[sym v] (first binding)] + {sym v}) + + 2 (let [[[[sym _]] [[schema v]]] ((juxt filter remove) #(= (val %) :-) binding)] + {sym v}))) + +;; TODO: unify this with positional version. +(defn letk-arg-bind-sym-and-body-form + "Given a single element of a single letk binding form and a current body form, return + a map {:schema-entry :body-form} where schema-entry is a tuple + [bound-key schema external-schema?], and body-form wraps body with destructuring + for this binding as necessary." + [env map-sym binding key-path body-form] + (cond (symbol? binding) + {:schema-entry [] + :body-form `(let [~(name-sym binding) (get ~map-sym ~(keyword binding) ~key-path)] + ~body-form)} + + (map? binding) + (let [schema-fixed-binding (process-schematized-map env binding) + [bound-sym opt-val-expr] (first schema-fixed-binding) + bound-key (keyword bound-sym)] + {:schema-entry [] + :body-form `(let [~(name-sym bound-sym) (get ~map-sym ~bound-key ~opt-val-expr)] + ~body-form)}) + + (vector? binding) + (let [[bound-key & more] binding + {inner-input-schema :input-schema + inner-external-input-schema :external-input-schema + inner-map-sym :map-sym + inner-body-form :body-form} (letk-input-schema-and-body-form + env + (with-meta (vec more) (meta binding)) + (conj key-path bound-key) + body-form)] + {:schema-entry [] + :body-form `(let [~inner-map-sym (get ~map-sym ~bound-key ~key-path)] + ~inner-body-form)}) + + :else (throw (ex-info (format "bad binding: %s" binding) {})))) + +(defn- extract-special-args + "Extract trailing & sym and :as sym, possibly with schema metadata. Returns + [more-bindings special-args-map] where special-args-map is a map from each + special symbol found to the symbol that was found." + [env special-arg-signifier-set binding-form] + {:pre [(set? special-arg-signifier-set)]} + (let [[more-bindings special-bindings] (split-with (complement special-arg-signifier-set) binding-form)] + (loop [special-args-map {} + special-arg-set special-arg-signifier-set + [arg-signifier & other-bindings :as special-bindings] special-bindings] + (if-not (seq special-bindings) + [more-bindings special-args-map] + (do + (let [[sym remaining-bindings] (schema-macros/extract-arrow-schematized-element env other-bindings)] + (recur (assoc special-args-map arg-signifier sym) + (disj special-arg-set arg-signifier) + remaining-bindings))))))) + +(defn letk-input-schema-and-body-form + "Given a single letk binding form, value form, key path, and body + form, return a map {:input-schema :external-input-schema :map-sym :body-form} + where input-schema is the schema imposed by binding-form, external-input-schema + is like input-schema but includes user overrides for binding vectors, + map-sym is the symbol which it expects the bound value to be bound to, + and body-form wraps body in the bindings from binding-form from map-sym." + [env binding-form key-path body-form] + (let [[bindings {more-sym '& as-sym :as}] (extract-special-args env #{'& :as} binding-form) + as-sym (or as-sym (gensym "map")) + [input-schema-elts + external-input-schema-elts + bound-body-form] (reduce + (fn [[input-schema-elts external-input-schema-elts cur-body] binding] + (let [{:keys [schema-entry body-form]} + (letk-arg-bind-sym-and-body-form + env as-sym binding key-path cur-body) + [bound-key input-schema external-input-schema] schema-entry] + [(conj input-schema-elts [bound-key input-schema]) + (conj external-input-schema-elts + [bound-key (or external-input-schema input-schema)]) + body-form])) + [[] [] body-form] + (reverse + (schema-macros/process-arrow-schematized-args + env bindings))) + explicit-schema-keys [] + final-body-form (if more-sym + `(let [~more-sym (dissoc ~as-sym ~@explicit-schema-keys)] + ~bound-body-form) + bound-body-form)] + {:map-sym as-sym + :body-form final-body-form})) diff --git a/dev/compojure_api_kondo_hooks/plumbing/fnk/schema.clj b/dev/compojure_api_kondo_hooks/plumbing/fnk/schema.clj new file mode 100644 index 00000000..b69379e0 --- /dev/null +++ b/dev/compojure_api_kondo_hooks/plumbing/fnk/schema.clj @@ -0,0 +1,16 @@ + +(s/defn unwrap-schema-form-key :- (s/maybe (s/pair s/Keyword "k" s/Bool "optional?")) + "Given a possibly-unevaluated schema map key form, unpack an explicit keyword + and optional? flag, or return nil for a non-explicit key" + [k] + (cond (s/specific-key? k) + [(s/explicit-schema-key k) (s/required-key? k)] + + ;; Deal with `(s/optional-key k) form from impl + (and (sequential? k) (not (vector? k)) (= (count k) 2) + (= (first k) 'schema.core/optional-key)) + [(second k) false] + + ;; Deal with `(with-meta ...) form from impl + (and (sequential? k) (not (vector? k)) (= (first k) `with-meta)) + (unwrap-schema-form-key (second k)))) diff --git a/dev/compojure_api_kondo_hooks/schema/macros.clj b/dev/compojure_api_kondo_hooks/schema/macros.clj new file mode 100644 index 00000000..aefe1448 --- /dev/null +++ b/dev/compojure_api_kondo_hooks/schema/macros.clj @@ -0,0 +1,24 @@ +(ns compojure-api-kondo-hooks.schema.macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Helpers for processing and normalizing element/argument schemas in s/defrecord and s/(de)fn + +(defn extract-arrow-schematized-element + "Take a nonempty seq, which may start like [a ...] or [a :- schema ...], and return + a list of [first-element-with-schema-attached rest-elements]" + [env s] + (assert (seq s)) + (let [[f & more] s] + (if (= :- (first more)) + [f (drop 2 more)] + [f more]))) + +(defn process-arrow-schematized-args + "Take an arg vector, in which each argument is followed by an optional :- schema, + and transform into an ordinary arg vector where the schemas are metadata on the args." + [env args] + (loop [in args out []] + (if (empty? in) + out + (let [[arg more] (extract-arrow-schematized-element env in)] + (recur more (conj out arg)))))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj index f2553622..ab1ead60 100644 --- a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure/api/meta.clj @@ -3,7 +3,8 @@ [compojure.api.common :as common :refer [extract-parameters]] [compojure.api.middleware #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) mw] [compojure.api.routes #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) routes] - [plumbing.core #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) p] + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [compojure-api-kondo-hooks.plumbing.core :as p] + :default [plumbing.core :as p]) [plumbing.fnk.impl #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) fnk-impl] [ring.swagger.common #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) rsc] [ring.swagger.json-schema #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) js] @@ -388,6 +389,7 @@ form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) form `(comp-core/context ~path ~arg-with-request ~form)] + (prn "context" form) form) ;; endpoints @@ -398,6 +400,7 @@ form `(fn [~'+compojure-api-request+] ~'+compojure-api-request+ ;;always used ~form)] + (prn "endpoint" form) form))) :default ;; JVM (if context? diff --git a/examples/clj-kondo-hooks/output/expected-output b/examples/clj-kondo-hooks/output/expected-output index a1a7842c..cf0b4345 100644 --- a/examples/clj-kondo-hooks/output/expected-output +++ b/examples/clj-kondo-hooks/output/expected-output @@ -1,3 +1,6 @@ src/compojure_api_example/clj_kondo_hooks.clj:26:20: error: keyword :ok is called with 0 args but expects 1 or 2 src/compojure_api_example/clj_kondo_hooks.clj:30:21: error: keyword :ok is called with 0 args but expects 1 or 2 -src/compojure_api_example/clj_kondo_hooks.clj:38:44: error: Unresolved symbol: req +src/compojure_api_example/clj_kondo_hooks.clj:33:17: error: Unresolved symbol: req +src/compojure_api_example/clj_kondo_hooks.clj:35:25: error: Unresolved symbol: body +src/compojure_api_example/clj_kondo_hooks.clj:38:17: error: Unresolved symbol: _ +src/compojure_api_example/clj_kondo_hooks.clj:46:29: error: Unresolved symbol: qparam diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj index f2553622..ab1ead60 100644 --- a/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj @@ -3,7 +3,8 @@ [compojure.api.common :as common :refer [extract-parameters]] [compojure.api.middleware #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) mw] [compojure.api.routes #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) routes] - [plumbing.core #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) p] + #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" [compojure-api-kondo-hooks.plumbing.core :as p] + :default [plumbing.core :as p]) [plumbing.fnk.impl #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) fnk-impl] [ring.swagger.common #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) rsc] [ring.swagger.json-schema #?(:default #_"the redundant :default is intentional, see ./scripts/regen_kondo_config.clj" :as-alias :default :as) js] @@ -388,6 +389,7 @@ form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) form `(comp-core/context ~path ~arg-with-request ~form)] + (prn "context" form) form) ;; endpoints @@ -398,6 +400,7 @@ form `(fn [~'+compojure-api-request+] ~'+compojure-api-request+ ;;always used ~form)] + (prn "endpoint" form) form))) :default ;; JVM (if context? diff --git a/src/compojure/api/meta.cljc b/src/compojure/api/meta.cljc index 16370800..712a576f 100644 --- a/src/compojure/api/meta.cljc +++ b/src/compojure/api/meta.cljc @@ -3,7 +3,8 @@ [compojure.api.common :as common :refer [extract-parameters]] [compojure.api.middleware #?(:clj-kondo :as-alias :default :as) mw] [compojure.api.routes #?(:clj-kondo :as-alias :default :as) routes] - [plumbing.core #?(:clj-kondo :as-alias :default :as) p] + #?(:clj-kondo [compojure-api-kondo-hooks.plumbing.core :as p] + :default [plumbing.core :as p]) [plumbing.fnk.impl #?(:clj-kondo :as-alias :default :as) fnk-impl] [ring.swagger.common #?(:clj-kondo :as-alias :default :as) rsc] [ring.swagger.json-schema #?(:clj-kondo :as-alias :default :as) js] @@ -388,6 +389,7 @@ form (if (seq letks) `(p/letk ~letks ~form) form) form (if (seq lets) `(let ~lets ~form) form) form `(comp-core/context ~path ~arg-with-request ~form)] + (prn "context" form) form) ;; endpoints @@ -398,6 +400,7 @@ form `(fn [~'+compojure-api-request+] ~'+compojure-api-request+ ;;always used ~form)] + (prn "endpoint" form) form))) :default ;; JVM (if context? From 2c1f1e271c5099daa6a046a80c0eb8cde256a803 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 18:06:44 -0500 Subject: [PATCH 39/39] wip --- .../plumbing/core.clj | 6 +- .../plumbing/core.clj | 42 +++++++ .../plumbing/fnk/impl.clj | 116 ++++++++++++++++++ .../schema/macros.clj | 24 ++++ .../plumbing/core.clj | 42 +++++++ .../plumbing/fnk/impl.clj | 116 ++++++++++++++++++ .../schema/macros.clj | 24 ++++ scripts/regen-kondo.clj | 2 + scripts/regen_kondo_config.clj | 6 +- 9 files changed, 372 insertions(+), 6 deletions(-) create mode 100644 examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj create mode 100644 examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj create mode 100644 examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj create mode 100644 resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj diff --git a/dev/compojure_api_kondo_hooks/plumbing/core.clj b/dev/compojure_api_kondo_hooks/plumbing/core.clj index 5b6d1965..5d0189a7 100644 --- a/dev/compojure_api_kondo_hooks/plumbing/core.clj +++ b/dev/compojure_api_kondo_hooks/plumbing/core.clj @@ -2,9 +2,7 @@ "Utility belt for Clojure in the wild" (:refer-clojure :exclude [update]) (:require - [schema.utils :as schema-utils] - [schema.macros :as schema-macros] - [plumbing.fnk.schema :as schema] + [compojure-api-kondo-hooks.schema.macros :as schema-macros] [compojure-api-kondo-hooks.plumbing.fnk.impl :as fnk-impl])) (defmacro letk @@ -30,8 +28,6 @@ Map destructuring bindings can be mixed with ordinary symbol bindings." [bindings & body] - (schema/assert-iae (vector? bindings) "Letk binding must be a vector") - (schema/assert-iae (even? (count bindings)) "Letk binding must have even number of elements") (reduce (fn [cur-body-form [bind-form value-form]] (if (symbol? bind-form) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj new file mode 100644 index 00000000..5d0189a7 --- /dev/null +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj @@ -0,0 +1,42 @@ +(ns compojure-api-kondo-hooks.plumbing.core + "Utility belt for Clojure in the wild" + (:refer-clojure :exclude [update]) + (:require + [compojure-api-kondo-hooks.schema.macros :as schema-macros] + [compojure-api-kondo-hooks.plumbing.fnk.impl :as fnk-impl])) + +(defmacro letk + "Keyword let. Accepts an interleaved sequence of binding forms and map forms like: + (letk [[a {b 2} [:f g h] c d {e 4} :as m & more] a-map ...] & body) + a, c, d, and f are required keywords, and letk will barf if not in a-map. + b and e are optional, and will be bound to default values if not present. + g and h are required keys in the map found under :f. + m will be bound to the entire map (a-map). + more will be bound to all the unbound keys (ie (dissoc a-map :a :b :c :d :e)). + :as and & are both optional, but must be at the end in the specified order if present. + The same symbol cannot be bound multiple times within the same destructing level. + + Optional values can reference symbols bound earlier within the same binding, i.e., + (= [2 2] (let [a 1] (letk [[a {b a}] {:a 2}] [a b]))) but + (= [2 1] (let [a 1] (letk [[{b a} a] {:a 2}] [a b]))) + + If present, :as and :& symbols are bound before other symbols within the binding. + + Namespaced keys are supported by specifying fully-qualified key in binding form. The bound + symbol uses the _name_ portion of the namespaced key, i.e, + (= 1 (letk [[a/b] {:a/b 1}] b)). + + Map destructuring bindings can be mixed with ordinary symbol bindings." + [bindings & body] + (reduce + (fn [cur-body-form [bind-form value-form]] + (if (symbol? bind-form) + `(let [~bind-form ~value-form] ~cur-body-form) + (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form + &env + bind-form ;(fnk-impl/ensure-schema-metadata &env bind-form) + [] + cur-body-form)] + `(let [~map-sym ~value-form] ~body-form)))) + `(do ~@body) + (reverse (partition 2 bindings)))) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj new file mode 100644 index 00000000..5e1ad75f --- /dev/null +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj @@ -0,0 +1,116 @@ +(ns compojure-api-kondo-hooks.plumbing.fnk.impl + (:require + [clojure.set :as set] + [schema.core :as-alias s] + [compojure-api-kondo-hooks.schema.macros :as schema-macros])) + +;;;;; Helpers + +(defn name-sym + "Returns symbol of x's name. + Converts a keyword/string to symbol, or removes namespace (if any) of symbol" + [x] + (with-meta (symbol (name x)) (meta x))) + +;;; Parsing new fnk binding style + +(declare letk-input-schema-and-body-form) + +(defn- process-schematized-map + "Take an optional binding map like {a 2} or {a :- Number 2} and convert the schema + information to canonical metadata, if present." + [env binding] + (case (count binding) + 1 (let [[sym v] (first binding)] + {sym v}) + + 2 (let [[[[sym _]] [[schema v]]] ((juxt filter remove) #(= (val %) :-) binding)] + {sym v}))) + +;; TODO: unify this with positional version. +(defn letk-arg-bind-sym-and-body-form + "Given a single element of a single letk binding form and a current body form, return + a map {:schema-entry :body-form} where schema-entry is a tuple + [bound-key schema external-schema?], and body-form wraps body with destructuring + for this binding as necessary." + [env map-sym binding key-path body-form] + (cond (symbol? binding) + {:schema-entry [] + :body-form `(let [~(name-sym binding) (get ~map-sym ~(keyword binding) ~key-path)] + ~body-form)} + + (map? binding) + (let [schema-fixed-binding (process-schematized-map env binding) + [bound-sym opt-val-expr] (first schema-fixed-binding) + bound-key (keyword bound-sym)] + {:schema-entry [] + :body-form `(let [~(name-sym bound-sym) (get ~map-sym ~bound-key ~opt-val-expr)] + ~body-form)}) + + (vector? binding) + (let [[bound-key & more] binding + {inner-input-schema :input-schema + inner-external-input-schema :external-input-schema + inner-map-sym :map-sym + inner-body-form :body-form} (letk-input-schema-and-body-form + env + (with-meta (vec more) (meta binding)) + (conj key-path bound-key) + body-form)] + {:schema-entry [] + :body-form `(let [~inner-map-sym (get ~map-sym ~bound-key ~key-path)] + ~inner-body-form)}) + + :else (throw (ex-info (format "bad binding: %s" binding) {})))) + +(defn- extract-special-args + "Extract trailing & sym and :as sym, possibly with schema metadata. Returns + [more-bindings special-args-map] where special-args-map is a map from each + special symbol found to the symbol that was found." + [env special-arg-signifier-set binding-form] + {:pre [(set? special-arg-signifier-set)]} + (let [[more-bindings special-bindings] (split-with (complement special-arg-signifier-set) binding-form)] + (loop [special-args-map {} + special-arg-set special-arg-signifier-set + [arg-signifier & other-bindings :as special-bindings] special-bindings] + (if-not (seq special-bindings) + [more-bindings special-args-map] + (do + (let [[sym remaining-bindings] (schema-macros/extract-arrow-schematized-element env other-bindings)] + (recur (assoc special-args-map arg-signifier sym) + (disj special-arg-set arg-signifier) + remaining-bindings))))))) + +(defn letk-input-schema-and-body-form + "Given a single letk binding form, value form, key path, and body + form, return a map {:input-schema :external-input-schema :map-sym :body-form} + where input-schema is the schema imposed by binding-form, external-input-schema + is like input-schema but includes user overrides for binding vectors, + map-sym is the symbol which it expects the bound value to be bound to, + and body-form wraps body in the bindings from binding-form from map-sym." + [env binding-form key-path body-form] + (let [[bindings {more-sym '& as-sym :as}] (extract-special-args env #{'& :as} binding-form) + as-sym (or as-sym (gensym "map")) + [input-schema-elts + external-input-schema-elts + bound-body-form] (reduce + (fn [[input-schema-elts external-input-schema-elts cur-body] binding] + (let [{:keys [schema-entry body-form]} + (letk-arg-bind-sym-and-body-form + env as-sym binding key-path cur-body) + [bound-key input-schema external-input-schema] schema-entry] + [(conj input-schema-elts [bound-key input-schema]) + (conj external-input-schema-elts + [bound-key (or external-input-schema input-schema)]) + body-form])) + [[] [] body-form] + (reverse + (schema-macros/process-arrow-schematized-args + env bindings))) + explicit-schema-keys [] + final-body-form (if more-sym + `(let [~more-sym (dissoc ~as-sym ~@explicit-schema-keys)] + ~bound-body-form) + bound-body-form)] + {:map-sym as-sym + :body-form final-body-form})) diff --git a/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj new file mode 100644 index 00000000..aefe1448 --- /dev/null +++ b/examples/clj-kondo-hooks/.clj-kondo/imports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj @@ -0,0 +1,24 @@ +(ns compojure-api-kondo-hooks.schema.macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Helpers for processing and normalizing element/argument schemas in s/defrecord and s/(de)fn + +(defn extract-arrow-schematized-element + "Take a nonempty seq, which may start like [a ...] or [a :- schema ...], and return + a list of [first-element-with-schema-attached rest-elements]" + [env s] + (assert (seq s)) + (let [[f & more] s] + (if (= :- (first more)) + [f (drop 2 more)] + [f more]))) + +(defn process-arrow-schematized-args + "Take an arg vector, in which each argument is followed by an optional :- schema, + and transform into an ordinary arg vector where the schemas are metadata on the args." + [env args] + (loop [in args out []] + (if (empty? in) + out + (let [[arg more] (extract-arrow-schematized-element env in)] + (recur more (conj out arg)))))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj new file mode 100644 index 00000000..5d0189a7 --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj @@ -0,0 +1,42 @@ +(ns compojure-api-kondo-hooks.plumbing.core + "Utility belt for Clojure in the wild" + (:refer-clojure :exclude [update]) + (:require + [compojure-api-kondo-hooks.schema.macros :as schema-macros] + [compojure-api-kondo-hooks.plumbing.fnk.impl :as fnk-impl])) + +(defmacro letk + "Keyword let. Accepts an interleaved sequence of binding forms and map forms like: + (letk [[a {b 2} [:f g h] c d {e 4} :as m & more] a-map ...] & body) + a, c, d, and f are required keywords, and letk will barf if not in a-map. + b and e are optional, and will be bound to default values if not present. + g and h are required keys in the map found under :f. + m will be bound to the entire map (a-map). + more will be bound to all the unbound keys (ie (dissoc a-map :a :b :c :d :e)). + :as and & are both optional, but must be at the end in the specified order if present. + The same symbol cannot be bound multiple times within the same destructing level. + + Optional values can reference symbols bound earlier within the same binding, i.e., + (= [2 2] (let [a 1] (letk [[a {b a}] {:a 2}] [a b]))) but + (= [2 1] (let [a 1] (letk [[{b a} a] {:a 2}] [a b]))) + + If present, :as and :& symbols are bound before other symbols within the binding. + + Namespaced keys are supported by specifying fully-qualified key in binding form. The bound + symbol uses the _name_ portion of the namespaced key, i.e, + (= 1 (letk [[a/b] {:a/b 1}] b)). + + Map destructuring bindings can be mixed with ordinary symbol bindings." + [bindings & body] + (reduce + (fn [cur-body-form [bind-form value-form]] + (if (symbol? bind-form) + `(let [~bind-form ~value-form] ~cur-body-form) + (let [{:keys [map-sym body-form]} (fnk-impl/letk-input-schema-and-body-form + &env + bind-form ;(fnk-impl/ensure-schema-metadata &env bind-form) + [] + cur-body-form)] + `(let [~map-sym ~value-form] ~body-form)))) + `(do ~@body) + (reverse (partition 2 bindings)))) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj new file mode 100644 index 00000000..5e1ad75f --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj @@ -0,0 +1,116 @@ +(ns compojure-api-kondo-hooks.plumbing.fnk.impl + (:require + [clojure.set :as set] + [schema.core :as-alias s] + [compojure-api-kondo-hooks.schema.macros :as schema-macros])) + +;;;;; Helpers + +(defn name-sym + "Returns symbol of x's name. + Converts a keyword/string to symbol, or removes namespace (if any) of symbol" + [x] + (with-meta (symbol (name x)) (meta x))) + +;;; Parsing new fnk binding style + +(declare letk-input-schema-and-body-form) + +(defn- process-schematized-map + "Take an optional binding map like {a 2} or {a :- Number 2} and convert the schema + information to canonical metadata, if present." + [env binding] + (case (count binding) + 1 (let [[sym v] (first binding)] + {sym v}) + + 2 (let [[[[sym _]] [[schema v]]] ((juxt filter remove) #(= (val %) :-) binding)] + {sym v}))) + +;; TODO: unify this with positional version. +(defn letk-arg-bind-sym-and-body-form + "Given a single element of a single letk binding form and a current body form, return + a map {:schema-entry :body-form} where schema-entry is a tuple + [bound-key schema external-schema?], and body-form wraps body with destructuring + for this binding as necessary." + [env map-sym binding key-path body-form] + (cond (symbol? binding) + {:schema-entry [] + :body-form `(let [~(name-sym binding) (get ~map-sym ~(keyword binding) ~key-path)] + ~body-form)} + + (map? binding) + (let [schema-fixed-binding (process-schematized-map env binding) + [bound-sym opt-val-expr] (first schema-fixed-binding) + bound-key (keyword bound-sym)] + {:schema-entry [] + :body-form `(let [~(name-sym bound-sym) (get ~map-sym ~bound-key ~opt-val-expr)] + ~body-form)}) + + (vector? binding) + (let [[bound-key & more] binding + {inner-input-schema :input-schema + inner-external-input-schema :external-input-schema + inner-map-sym :map-sym + inner-body-form :body-form} (letk-input-schema-and-body-form + env + (with-meta (vec more) (meta binding)) + (conj key-path bound-key) + body-form)] + {:schema-entry [] + :body-form `(let [~inner-map-sym (get ~map-sym ~bound-key ~key-path)] + ~inner-body-form)}) + + :else (throw (ex-info (format "bad binding: %s" binding) {})))) + +(defn- extract-special-args + "Extract trailing & sym and :as sym, possibly with schema metadata. Returns + [more-bindings special-args-map] where special-args-map is a map from each + special symbol found to the symbol that was found." + [env special-arg-signifier-set binding-form] + {:pre [(set? special-arg-signifier-set)]} + (let [[more-bindings special-bindings] (split-with (complement special-arg-signifier-set) binding-form)] + (loop [special-args-map {} + special-arg-set special-arg-signifier-set + [arg-signifier & other-bindings :as special-bindings] special-bindings] + (if-not (seq special-bindings) + [more-bindings special-args-map] + (do + (let [[sym remaining-bindings] (schema-macros/extract-arrow-schematized-element env other-bindings)] + (recur (assoc special-args-map arg-signifier sym) + (disj special-arg-set arg-signifier) + remaining-bindings))))))) + +(defn letk-input-schema-and-body-form + "Given a single letk binding form, value form, key path, and body + form, return a map {:input-schema :external-input-schema :map-sym :body-form} + where input-schema is the schema imposed by binding-form, external-input-schema + is like input-schema but includes user overrides for binding vectors, + map-sym is the symbol which it expects the bound value to be bound to, + and body-form wraps body in the bindings from binding-form from map-sym." + [env binding-form key-path body-form] + (let [[bindings {more-sym '& as-sym :as}] (extract-special-args env #{'& :as} binding-form) + as-sym (or as-sym (gensym "map")) + [input-schema-elts + external-input-schema-elts + bound-body-form] (reduce + (fn [[input-schema-elts external-input-schema-elts cur-body] binding] + (let [{:keys [schema-entry body-form]} + (letk-arg-bind-sym-and-body-form + env as-sym binding key-path cur-body) + [bound-key input-schema external-input-schema] schema-entry] + [(conj input-schema-elts [bound-key input-schema]) + (conj external-input-schema-elts + [bound-key (or external-input-schema input-schema)]) + body-form])) + [[] [] body-form] + (reverse + (schema-macros/process-arrow-schematized-args + env bindings))) + explicit-schema-keys [] + final-body-form (if more-sym + `(let [~more-sym (dissoc ~as-sym ~@explicit-schema-keys)] + ~bound-body-form) + bound-body-form)] + {:map-sym as-sym + :body-form final-body-form})) diff --git a/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj new file mode 100644 index 00000000..aefe1448 --- /dev/null +++ b/resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj @@ -0,0 +1,24 @@ +(ns compojure-api-kondo-hooks.schema.macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Helpers for processing and normalizing element/argument schemas in s/defrecord and s/(de)fn + +(defn extract-arrow-schematized-element + "Take a nonempty seq, which may start like [a ...] or [a :- schema ...], and return + a list of [first-element-with-schema-attached rest-elements]" + [env s] + (assert (seq s)) + (let [[f & more] s] + (if (= :- (first more)) + [f (drop 2 more)] + [f more]))) + +(defn process-arrow-schematized-args + "Take an arg vector, in which each argument is followed by an optional :- schema, + and transform into an ordinary arg vector where the schemas are metadata on the args." + [env args] + (loop [in args out []] + (if (empty? in) + out + (let [[arg more] (extract-arrow-schematized-element env in)] + (recur more (conj out arg)))))) diff --git a/scripts/regen-kondo.clj b/scripts/regen-kondo.clj index c9bdbfa3..a5300986 100755 --- a/scripts/regen-kondo.clj +++ b/scripts/regen-kondo.clj @@ -5,5 +5,7 @@ set -ex rm -r resources/clj-kondo.exports mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure/api mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure +mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk +mkdir -p resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/schema bb -f ./scripts/regen_kondo_config.clj diff --git a/scripts/regen_kondo_config.clj b/scripts/regen_kondo_config.clj index 8678848c..65bb0477 100755 --- a/scripts/regen_kondo_config.clj +++ b/scripts/regen_kondo_config.clj @@ -8,7 +8,11 @@ {"src/compojure/api/common.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/common.clj" "src/compojure/api/core.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/core.clj" "src/compojure/api/meta.cljc" "resources/clj-kondo.exports/metosin/compojure-api/compojure/api/meta.clj" - "dev/compojure_api_kondo_hooks/compojure/core.clj" "resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj"}) + "dev/compojure_api_kondo_hooks/compojure/core.clj" "resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/compojure/core.clj" + "dev/compojure_api_kondo_hooks/plumbing/core.clj" "resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/core.clj" + "dev/compojure_api_kondo_hooks/schema/macros.clj" "resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/schema/macros.clj" + "dev/compojure_api_kondo_hooks/plumbing/fnk/impl.clj" "resources/clj-kondo.exports/metosin/compojure-api/compojure_api_kondo_hooks/plumbing/fnk/impl.clj" + }) (def restructured-macro-names '#{context GET ANY HEAD PATCH DELETE OPTIONS POST PUT})