Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Namespace alias support for spec-list and spec-form #42

Merged
merged 4 commits into from
Mar 21, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 62 additions & 19 deletions src/orchard/spec.clj
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,43 @@
(str %))
form))

(defn- ns-name->ns-alias
"Return mapping from full namespace name to its alias in the given namespace."
[^String ns]
(if ns
(reduce-kv (fn [m alias ns]
(assoc m (name (ns-name ns)) (name alias)))
{}
(ns-aliases (symbol ns)))
{}))

(defn spec-list
"Retrieves a list of all specs in the registry, sorted by ns/name.
If filter-regex is not empty, keep only the specs with that prefix."
[filter-regex]
(let [sorted-specs (->> (registry)
keys
(map str)
sort)]
(if (not-empty filter-regex)
(filter (fn [spec-symbol-str]
(let [checkable-part (if (.startsWith ^String spec-symbol-str ":")
(subs spec-symbol-str 1)
spec-symbol-str)]
(re-find (re-pattern filter-regex) checkable-part)))
sorted-specs)
sorted-specs)))
([filter-regex]
(spec-list filter-regex nil))
([filter-regex ns]
(let [ns-alias (ns-name->ns-alias ns)
sorted-specs (->> (registry)
keys
(mapcat (fn [kw]
;; Return an aliased entry in the current ns (if any)
;; with the fully qualified keyword
(let [keyword-ns (namespace kw)]
(if (= ns keyword-ns)
[(str kw) (str "::" (name kw))]
(if-let [alias (ns-alias keyword-ns)]
[(str kw) (str "::" alias "/" (name kw))]
[(str kw)])))))
sort)]
(if (not-empty filter-regex)
(filter (fn [spec-symbol-str]
(let [checkable-part (if (.startsWith ^String spec-symbol-str ":")
(subs spec-symbol-str 1)
spec-symbol-str)]
(re-find (re-pattern filter-regex) checkable-part)))
sorted-specs)
sorted-specs))))

(defn get-multi-spec-sub-specs
"Given a multi-spec form, call its multi method methods to retrieve
Expand Down Expand Up @@ -109,15 +130,37 @@
form))
sub-form))

(defn- expand-ns-alias
"Expand a possible ns aliased keyword into a fully qualified keyword."
[^String ns ^String spec-name]
(if (and ns (.startsWith spec-name "::"))
(let [slash (.indexOf spec-name "/")]
(if (= -1 slash)
;; This is a keyword in the current namespace
(str ":" ns "/" (subs spec-name 2))

;; This is a keyword in an aliased namespace
(let [[keyword-ns kw] (.split (subs spec-name 2) "/")
aliases (ns-aliases (symbol ns))
ns-name (some-> keyword-ns symbol aliases ns-name name)]
(if ns-name
(str ":" ns-name "/" kw)
spec-name))))

;; Nothing to expand
spec-name))

(defn spec-form
"Given a spec symbol as a string, get the spec form and prepare it for
a response."
[spec-name]
(when-let [spec (spec-from-string spec-name)]
(-> (form spec)
add-multi-specs
normalize-spec-form
str-non-colls)))
([spec-name]
(spec-form spec-name nil))
([spec-name ns]
(when-let [spec (spec-from-string (expand-ns-alias ns spec-name))]
(-> (form spec)
add-multi-specs
normalize-spec-form
str-non-colls))))

(defn spec-example
"Given a spec symbol as a string, returns a string with a pretty printed
Expand Down
32 changes: 32 additions & 0 deletions test/orchard/spec_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,35 @@
(clojure.core/fn [%] (clojure.core/< (:start %) (:end %))))
:ret (clojure.core/fn [%] (clojure.core/> (:start %) (:end %)))
:fn nil)))))

(def spec-available? (or (resolve (symbol "clojure.spec.alpha" "get-spec"))
tatut marked this conversation as resolved.
Show resolved Hide resolved
(resolve (symbol "clojure.spec" "get-spec"))))

(when spec-available?
(deftest spec-is-found-by-ns-alias
(testing "current ns keyword"
(testing "spec-list finds current ns keyword"
(eval '(clojure.spec.alpha/def ::foo string?))
(let [specs (into #{}
(spec/spec-list "" "orchard.spec-test"))]
(is (specs "::foo") "Spec is found with current ns")
(is (specs ":orchard.spec-test/foo") "Spec is found with fully qualified name")))

(testing "spec-form finds current ns keyword"
(let [spec1 (spec/spec-form "::foo" "orchard.spec-test")
spec2 (spec/spec-form ":orchard.spec-test/foo" "orchard.spec-test")]
(is (= "clojure.core/string?" spec1 spec2) "Both return the same correct spec"))))

(testing "ns aliased keyword"
(eval '(clojure.spec.alpha/def :orchard.spec/test-dummy boolean?))
(testing "spec-list finds keyword in aliased namespace"

(let [specs (into #{}
(spec/spec-list "" "orchard.spec-test"))]
(is (specs "::spec/test-dummy") "Spec is found with ns-aliased keyword")
(is (specs ":orchard.spec/test-dummy") "Spec is found with fully qualified name")))

(testing "spec-form finds keyword in aliased namespace"
(let [spec1 (spec/spec-form "::spec/test-dummy" "orchard.spec-test")
spec2 (spec/spec-form ":orchard.spec/test-dummy" "orchard.spec-test")]
(is (= "clojure.core/boolean?" spec1 spec2) "Both return the same correct spec"))))))