From c967ffc51fb5f393869406b9003a1b080fb3bea5 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Fri, 30 Aug 2024 18:34:53 -0500 Subject: [PATCH] Close #453: best-effort ordered s/enum print via cached field --- CHANGELOG.md | 3 +++ src/cljc/schema/core.cljc | 14 +++++++++++--- test/cljc/schema/core_test.cljc | 24 +++++++++++++++++++++++- 3 files changed, 37 insertions(+), 4 deletions(-) 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..8d773f0c 100644 --- a/src/cljc/schema/core.cljc +++ b/src/cljc/schema/core.cljc @@ -284,16 +284,24 @@ ;;; enum (in a set of allowed values) +(defn- best-effort-ordered-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] (best-effort-ordered-form this))) (clojure.core/defn enum "A value that must be = to some element of vs." [& vs] - (EnumSchema. (set vs))) - + (let [svs (set vs)] + (EnumSchema. svs + ;;meta + {::form-hint {svs (delay (seq (into ['enum] (distinct) vs)))}} + ;;ext + nil))) ;;; 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?)]