Skip to content

Commit fc26fae

Browse files
committed
Close #453: best-effort ordered s/enum print via cached field
1 parent 8fcb57f commit fc26fae

File tree

3 files changed

+37
-4
lines changed

3 files changed

+37
-4
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## NEXT
2+
* [#449](https://github.com/plumatic/schema/issues/453): Preserve `s/enum` order during printing
3+
14
## 1.4.1 (`2022-09-29`)
25
* [#449](https://github.com/plumatic/schema/issues/449): Fix bad jsdoc
36

src/cljc/schema/core.cljc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,16 +284,24 @@
284284

285285
;;; enum (in a set of allowed values)
286286

287+
;; breaks if :vs is set manually without reconstructing via s/enum
288+
(defn- usually-ordered-enum-form [{:keys [vs] :as enum}]
289+
(or (-> enum meta ::form-hint (get vs) force)
290+
(cons 'enum vs)))
291+
287292
(macros/defrecord-schema EnumSchema [vs]
288293
Schema
289294
(spec [this] (leaf/leaf-spec (spec/precondition this #(contains? vs %) #(list vs %))))
290-
(explain [this] (cons 'enum vs)))
295+
(explain [this] (usually-ordered-enum-form this)))
291296

292297
(clojure.core/defn enum
293298
"A value that must be = to some element of vs."
294299
[& vs]
295-
(EnumSchema. (set vs)))
296-
300+
(let [svs (set vs)]
301+
;; TODO it would be nice to use the (EnumSchema. vs _meta _ext) ctor but it doesn't work yet in bb
302+
;; https://github.com/babashka/sci/issues/928
303+
(-> (EnumSchema. svs)
304+
(with-meta {::form-hint {svs (delay (seq (into ['enum] (distinct) vs)))}}))))
297305

298306
;;; pred (matches all values for which p? returns truthy)
299307

test/cljc/schema/core_test.cljc

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,29 @@
133133
(valid! schema 1)
134134
(invalid! schema :c)
135135
(invalid! (s/enum :a) 2 "(not (#{:a} 2))")
136-
(is (= '(1 :a :b enum) (sort-by str (s/explain schema))))))
136+
(is (= '(enum :a :b 1) (s/explain schema))))
137+
(is (= (cons 'enum (range 1000)) (s/explain (apply s/enum (range 1000)))))
138+
(testing "prints as if (distinct vs), which preserves original order"
139+
(is (= '(enum 1 2 3 4) (s/explain (s/enum 1 2 1 3 1 4)))))
140+
(testing "equality still works if implementation details are exploited"
141+
(is (= (update (s/enum 1 2 3) :vs conj 4)
142+
(update (s/enum 1 2 3 4 5) :vs disj 5))))
143+
(testing "still prints correctly (albeit unordered) if implementation details are exploited"
144+
(dotimes [_ 100]
145+
(let [[a b c] (repeatedly #(rand-nth
146+
[(gensym)
147+
(str (gensym))
148+
(keyword (gensym))]))
149+
_ (assert (distinct? a b c))
150+
e (s/enum a b)
151+
_ (testing "prints in order"
152+
(is (= (list 'enum a b) (s/explain e))))
153+
e (update e :vs conj c)
154+
_ (testing "adding an extra entry using implementation details just prints using the set's order"
155+
(is (= (cons 'enum (:vs e)) (s/explain e))))
156+
e (update e :vs disj c)
157+
_ (testing "resetting :vs preserves the original printing order"
158+
(is (= (list 'enum a b) (s/explain (update e :vs disj c)))))]))))
137159

138160
(deftest pred-test
139161
(let [schema (s/pred odd? 'odd?)]

0 commit comments

Comments
 (0)