diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a4f6cc..4bbd1d55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## NEXT + * [#449](https://github.com/plumatic/schema/issues/453): Preserve `s/enum` order during printing + ## 1.4.1 (`2022-09-29`) * [#449](https://github.com/plumatic/schema/issues/449): Fix bad jsdoc diff --git a/src/cljc/schema/core.cljc b/src/cljc/schema/core.cljc index 75d8fd1d..c9e2cba3 100644 --- a/src/cljc/schema/core.cljc +++ b/src/cljc/schema/core.cljc @@ -284,16 +284,24 @@ ;;; enum (in a set of allowed values) +;; breaks if :vs is set manually without reconstructing via s/enum +(defn- usually-ordered-enum-form [{:keys [vs] :as enum}] + (or (-> enum meta ::form-hint (get vs) force) + (cons 'enum vs))) + (macros/defrecord-schema EnumSchema [vs] Schema (spec [this] (leaf/leaf-spec (spec/precondition this #(contains? vs %) #(list vs %)))) - (explain [this] (cons 'enum vs))) + (explain [this] (usually-ordered-enum-form this))) (clojure.core/defn enum "A value that must be = to some element of vs." [& vs] - (EnumSchema. (set vs))) - + (let [svs (set vs)] + ;; TODO it would be nice to use the (EnumSchema. vs _meta _ext) ctor but it doesn't work yet in bb + ;; https://github.com/babashka/sci/issues/928 + (-> (EnumSchema. svs) + (with-meta {::form-hint {svs (delay (seq (into ['enum] (distinct) vs)))}})))) ;;; pred (matches all values for which p? returns truthy) diff --git a/test/cljc/schema/core_test.cljc b/test/cljc/schema/core_test.cljc index 7b12ed2c..928ed695 100644 --- a/test/cljc/schema/core_test.cljc +++ b/test/cljc/schema/core_test.cljc @@ -133,7 +133,29 @@ (valid! schema 1) (invalid! schema :c) (invalid! (s/enum :a) 2 "(not (#{:a} 2))") - (is (= '(1 :a :b enum) (sort-by str (s/explain schema)))))) + (is (= '(enum :a :b 1) (s/explain schema)))) + (is (= (cons 'enum (range 1000)) (s/explain (apply s/enum (range 1000))))) + (testing "prints as if (distinct vs), which preserves original order" + (is (= '(enum 1 2 3 4) (s/explain (s/enum 1 2 1 3 1 4))))) + (testing "equality still works if implementation details are exploited" + (is (= (update (s/enum 1 2 3) :vs conj 4) + (update (s/enum 1 2 3 4 5) :vs disj 5)))) + (testing "still prints correctly (albeit unordered) if implementation details are exploited" + (dotimes [_ 100] + (let [[a b c] (repeatedly #(rand-nth + [(gensym) + (str (gensym)) + (keyword (gensym))])) + _ (assert (distinct? a b c)) + e (s/enum a b) + _ (testing "prints in order" + (is (= (list 'enum a b) (s/explain e)))) + e (update e :vs conj c) + _ (testing "adding an extra entry using implementation details just prints using the set's order" + (is (= (cons 'enum (:vs e)) (s/explain e)))) + e (update e :vs disj c) + _ (testing "resetting :vs preserves the original printing order" + (is (= (list 'enum a b) (s/explain (update e :vs disj c)))))])))) (deftest pred-test (let [schema (s/pred odd? 'odd?)]