From cc7ae71b215936fc04cf5170cbeacd9157884788 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 13:46:30 -0500 Subject: [PATCH 01/24] :seqable / :every --- src/malli/core.cljc | 60 ++++++++++++++++++++++++++++----------- test/malli/core_test.cljc | 16 +++++++++++ 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index e353d7ec8..7adae7df7 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -638,7 +638,16 @@ (and max f) (fn [x] (<= (f x) max)) max (fn [x] (<= x max))))) -(defn -validate-limits [min max] (or ((-min-max-pred count) {:min min :max max}) (constantly true))) +(defn -validate-limits [min max] + (or ((-min-max-pred count) {:min min :max max}) (constantly true))) + +(defn -needed-bounded-checks [min max options] + (or (some-> max inc) + min + (:coll-check-limit options 101))) + +(defn -validate-bounded-limits [needed min max] + (or ((-min-max-pred #(bounded-count needed %)) {:min min :max max}) (constantly true))) (defn -qualified-keyword-pred [properties] (when-let [ns-name (some-> properties :namespace name)] @@ -1197,22 +1206,37 @@ (let [[schema :as children] (-vmap #(schema % options) children) form (delay (-simple-form parent properties children -form options)) cache (-create-cache options) - validate-limits (-validate-limits min max) + bounded (when (:bounded props) + (when fempty + (-fail! ::cannot-provide-empty-and-bounded-props)) + (-needed-bounded-checks min max options)) + _ (prn "bounded" bounded) + validate-limits (if (or min max) + (if bounded + (-validate-bounded-limits bounded min max) + (-validate-limits min max)) + any?) ->parser (fn [f g] (let [child-parser (f schema)] (fn [x] (cond (not (fpred x)) ::invalid (not (validate-limits x)) ::invalid - :else (let [x' (reduce - (fn [acc v] - (let [v' (child-parser v)] - (if (miu/-invalid? v') (reduced ::invalid) (conj acc v')))) - [] x)] - (cond - (miu/-invalid? x') x' - g (g x') - fempty (into fempty x') - :else x'))))))] + :else (if bounded + (let [child-validator child-parser] + (reduce + (fn [acc v] + (if (child-validator v) (reduced ::invalid) acc)) + x (eduction (take bounded) x))) + (let [x' (reduce + (fn [acc v] + (let [v' (child-parser v)] + (if (miu/-invalid? v') (reduced ::invalid) (conj acc v')))) + [] x)] + (cond + (miu/-invalid? x') x' + g (g x') + fempty (into fempty x') + :else x')))))))] ^{:type ::schema} (reify AST @@ -1222,20 +1246,22 @@ (let [validator (-validator schema)] (fn [x] (and (fpred x) (validate-limits x) - (reduce (fn [acc v] (if (validator v) acc (reduced false))) true x))))) + (reduce (fn [acc v] (if (validator v) acc (reduced false))) true + (cond->> x + bounded (eduction (take bounded)))))))) (-explainer [this path] (let [explainer (-explainer schema (conj path 0))] (fn [x in acc] (cond (not (fpred x)) (conj acc (miu/-error path in this x ::invalid-type)) (not (validate-limits x)) (conj acc (miu/-error path in this x ::limits)) - :else (let [size (count x)] + :else (let [size (or bounded (count x))] (loop [acc acc, i 0, [x & xs] x] (if (< i size) (cond-> (or (explainer x (conj in (fin i x)) acc) acc) xs (recur (inc i) xs)) acc))))))) - (-parser [_] (->parser -parser parse)) - (-unparser [_] (->parser -unparser unparse)) + (-parser [_] (->parser (if bounded -validator -parser) (if bounded identity parse))) + (-unparser [_] (->parser (if bounded -validator -unparser) (if bounded identity unparse))) (-transformer [this transformer method options] (let [collection? #(or (sequential? %) (set? %)) this-transformer (-value-transformer transformer this method options) @@ -2498,6 +2524,8 @@ :map-of (-map-of-schema) :vector (-collection-schema {:type :vector, :pred vector?, :empty []}) :sequential (-collection-schema {:type :sequential, :pred sequential?}) + :seqable (-collection-schema {:type :seqable, :pred seqable?}) + :every (-collection-schema {:type :every, :pred seqable? :bounded true}) :set (-collection-schema {:type :set, :pred set?, :empty #{}, :in (fn [_ x] x)}) :enum (-enum-schema) :maybe (-maybe-schema) diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 06ee7954c..cbadf0ab7 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3224,3 +3224,19 @@ ::xymap [:merge ::xmap ::ymap]}} ::xymap] {:registry registry, ::m/ref-key :id})))))))) + +(deftest seqable-schema-test + (is (m/validate [:seqable :int] #{1 2 3})) + (is (nil? (m/explain [:seqable :int] #{1 2 3}))) + (is (not (m/validate [:seqable :int] #{1 nil 3}))) + (is (= #{["should be an integer"]} + (me/humanize (m/explain [:seqable :int] #{1 nil 3}))))) + +(deftest every-schema-test + (is (m/validate [:every :int] #{1 2 3})) + (is (nil? (m/explain [:every :int] #{1 2 3}))) + (is (not (m/validate [:every :int] #{1 nil 3}))) + (is (m/validate [:every :int] (concat (range 1000) [nil]))) + (is (= #{["should be an integer"]} + (me/humanize (m/explain [:every :int] #{1 nil 3})))) + (is (nil? (m/explain [:every :int] (concat (range 1000) [nil]))))) From 6d5a381431e00ed39ba48288bb589b11d5db8628 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 13:49:01 -0500 Subject: [PATCH 02/24] rm --- src/malli/core.cljc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 7adae7df7..f8f37b10d 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1210,7 +1210,6 @@ (when fempty (-fail! ::cannot-provide-empty-and-bounded-props)) (-needed-bounded-checks min max options)) - _ (prn "bounded" bounded) validate-limits (if (or min max) (if bounded (-validate-bounded-limits bounded min max) From 4b87e289de4c6ba039c195cb14e23c03c30b6e6c Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 13:49:59 -0500 Subject: [PATCH 03/24] ws --- src/malli/core.cljc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index f8f37b10d..47e591308 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -638,8 +638,7 @@ (and max f) (fn [x] (<= (f x) max)) max (fn [x] (<= x max))))) -(defn -validate-limits [min max] - (or ((-min-max-pred count) {:min min :max max}) (constantly true))) +(defn -validate-limits [min max] (or ((-min-max-pred count) {:min min :max max}) (constantly true))) (defn -needed-bounded-checks [min max options] (or (some-> max inc) From b6bb1e5552ea5bbff1030ce03c0644d7e8d36746 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 13:50:25 -0500 Subject: [PATCH 04/24] ws --- src/malli/core.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 47e591308..30173a587 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -2523,7 +2523,7 @@ :vector (-collection-schema {:type :vector, :pred vector?, :empty []}) :sequential (-collection-schema {:type :sequential, :pred sequential?}) :seqable (-collection-schema {:type :seqable, :pred seqable?}) - :every (-collection-schema {:type :every, :pred seqable? :bounded true}) + :every (-collection-schema {:type :every, :pred seqable?, :bounded true}) :set (-collection-schema {:type :set, :pred set?, :empty #{}, :in (fn [_ x] x)}) :enum (-enum-schema) :maybe (-maybe-schema) From 2a25049ff53f552b449210774a3146edc4bfc43b Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 13:53:15 -0500 Subject: [PATCH 05/24] min/max tests --- test/malli/core_test.cljc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index cbadf0ab7..601e8f99d 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3237,6 +3237,15 @@ (is (nil? (m/explain [:every :int] #{1 2 3}))) (is (not (m/validate [:every :int] #{1 nil 3}))) (is (m/validate [:every :int] (concat (range 1000) [nil]))) + (is (m/validate [:every {:min 1000} :int] (concat (range 1000) [nil]))) + (is (not (m/validate [:every {:min 1001} :int] (concat (range 1000) [nil])))) + (is (m/validate [:every {:max 1000} :int] (range 1000))) + (is (not (m/validate [:every {:max 1000} :int] (range 1001)))) + (is (not (m/validate [:every {:max 1001} :int] (concat (range 1000) [nil])))) (is (= #{["should be an integer"]} (me/humanize (m/explain [:every :int] #{1 nil 3})))) - (is (nil? (m/explain [:every :int] (concat (range 1000) [nil]))))) + (is (nil? (m/explain [:every :int] (concat (range 1000) [nil])))) + (is (= (concat (repeat 1000 nil) [["should be an integer"]]) + (me/humanize (m/explain [:every {:min 1001} :int] (concat (range 1000) [nil]))))) + (is (= (concat (repeat 1000 nil) [["should be an integer"]]) + (me/humanize (m/explain [:every {:max 1001} :int] (concat (range 1000) [nil])))))) From 4f57d62dab8045705acddd7ea548a7b6ad7330ae Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 13:59:45 -0500 Subject: [PATCH 06/24] check all if counted or indexed --- src/malli/core.cljc | 11 ++++++++--- test/malli/core_test.cljc | 4 ++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 30173a587..8ab1f07b9 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1224,7 +1224,9 @@ (reduce (fn [acc v] (if (child-validator v) (reduced ::invalid) acc)) - x (eduction (take bounded) x))) + x (cond->> x + (or (counted? x) (indexed? x)) + (eduction (take bounded))))) (let [x' (reduce (fn [acc v] (let [v' (child-parser v)] @@ -1246,14 +1248,17 @@ (validate-limits x) (reduce (fn [acc v] (if (validator v) acc (reduced false))) true (cond->> x - bounded (eduction (take bounded)))))))) + (and bounded (not (counted? x)) (not (indexed? x))) + (eduction (take bounded)))))))) (-explainer [this path] (let [explainer (-explainer schema (conj path 0))] (fn [x in acc] (cond (not (fpred x)) (conj acc (miu/-error path in this x ::invalid-type)) (not (validate-limits x)) (conj acc (miu/-error path in this x ::limits)) - :else (let [size (or bounded (count x))] + :else (let [size (if (and bounded (not (counted? x)) (not (indexed? x))) + bounded + (count x))] (loop [acc acc, i 0, [x & xs] x] (if (< i size) (cond-> (or (explainer x (conj in (fin i x)) acc) acc) xs (recur (inc i) xs)) diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 601e8f99d..36795df8c 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3237,7 +3237,11 @@ (is (nil? (m/explain [:every :int] #{1 2 3}))) (is (not (m/validate [:every :int] #{1 nil 3}))) (is (m/validate [:every :int] (concat (range 1000) [nil]))) + ;; counted/indexed colls have everything validated + (is (not (m/validate [:every :int] (vec (concat (range 1000) [nil]))))) (is (m/validate [:every {:min 1000} :int] (concat (range 1000) [nil]))) + ;; counted/indexed colls have everything validated + (is (not (m/validate [:every {:min 1000} :int] (vec (concat (range 1000) [nil]))))) (is (not (m/validate [:every {:min 1001} :int] (concat (range 1000) [nil])))) (is (m/validate [:every {:max 1000} :int] (range 1000))) (is (not (m/validate [:every {:max 1000} :int] (range 1001)))) From 6934b6b8f64d90437c210f728cba7ed9e3152975 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 14:15:55 -0500 Subject: [PATCH 07/24] doc --- README.md | 42 +++++++++++++++++++++++++++++++++++++++ src/malli/core.cljc | 8 ++++---- test/malli/core_test.cljc | 11 ++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eb63a5e1a..6785fe298 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,48 @@ default branching can be arbitrarily nested: ; => true ``` +## Seqable schemas + +The `:seqable` and `:every` schemas describe `seqable?` collections. They +differ in their handling of `counted?` or `indexed?` collections and their +[parsers](#parsing-values) (`:seqable` parses its elements but `:every?` does not). + +`:seqable` validates the entire collection, while `:every` checks only the +largest of `:min`, `(inc :max)`, and `(:coll-check-limit options 101)`, or +the entire collection if the input is `counted?` or `indexed?`. + +```clojure +;; :seqable and :every validate identically with small, counted, or indexed collections. +(m/validate [:seqable :int] #{1 2 3}) +;=> true +(m/validate [:seqable :int] [1 2 3]) +;=> true +(m/validate [:seqable :int] (sorted-set 1 2 3)) +;=> true +(m/validate [:seqable :int] (range 1000)) +;=> true +(m/validate [:seqable :int] (conj (vec (range 1000)) nil)) +;=> false + +(m/validate [:every :int] #{1 2 3}) +;=> true +(m/validate [:every :int] [1 2 3]) +;=> true +(m/validate [:every :int] (sorted-set 1 2 3)) +;=> true +(m/validate [:every :int] (vec (range 1000))) +;=> true +(m/validate [:every :int] (conj (vec (range 1000)) nil)) +;=> false + +;; for large uncounted and unindexed collections, :every only checks a certain length +(m/validate [:seqable :int] (concat (range 1000) [nil])) +;=> false +(m/validate [:every :int] (concat (range 1000) [nil])) +;=> true +``` + + ## Sequence schemas You can use `:sequential` to describe homogeneous sequential Clojure collections. diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 8ab1f07b9..6552da152 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -641,9 +641,9 @@ (defn -validate-limits [min max] (or ((-min-max-pred count) {:min min :max max}) (constantly true))) (defn -needed-bounded-checks [min max options] - (or (some-> max inc) - min - (:coll-check-limit options 101))) + (c/max (or (some-> max inc) 0) + (or min 0) + (:coll-check-limit options 101))) (defn -validate-bounded-limits [needed min max] (or ((-min-max-pred #(bounded-count needed %)) {:min min :max max}) (constantly true))) @@ -1211,7 +1211,7 @@ (-needed-bounded-checks min max options)) validate-limits (if (or min max) (if bounded - (-validate-bounded-limits bounded min max) + (-validate-bounded-limits (c/min bounded (or max bounded)) min max) (-validate-limits min max)) any?) ->parser (fn [f g] (let [child-parser (f schema)] diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 36795df8c..4d545c869 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3227,6 +3227,13 @@ (deftest seqable-schema-test (is (m/validate [:seqable :int] #{1 2 3})) + (is (m/validate [:seqable :int] [1 2 3])) + (is (m/validate [:seqable :int] (sorted-set 1 2 3))) + (is (m/validate [:seqable :int] #{1 2 3})) + (is (m/validate [:seqable :int] (range 1000))) + (is (not (m/validate [:seqable :int] (conj (vec (range 1000)) nil)))) + (is (not (m/validate [:seqable :int] (concat (range 1000) [nil])))) + (is (not (m/validate [:seqable {:min 1000} :int] (concat (range 1000) [nil])))) (is (nil? (m/explain [:seqable :int] #{1 2 3}))) (is (not (m/validate [:seqable :int] #{1 nil 3}))) (is (= #{["should be an integer"]} @@ -3234,11 +3241,15 @@ (deftest every-schema-test (is (m/validate [:every :int] #{1 2 3})) + (is (m/validate [:every :int] [1 2 3])) + (is (m/validate [:every :int] (sorted-set 1 2 3))) + (is (not (m/validate [:every :int] (conj (vec (range 1000)) nil)))) (is (nil? (m/explain [:every :int] #{1 2 3}))) (is (not (m/validate [:every :int] #{1 nil 3}))) (is (m/validate [:every :int] (concat (range 1000) [nil]))) ;; counted/indexed colls have everything validated (is (not (m/validate [:every :int] (vec (concat (range 1000) [nil]))))) + (is (m/validate [:every :int] (concat (range 1000) [nil]))) (is (m/validate [:every {:min 1000} :int] (concat (range 1000) [nil]))) ;; counted/indexed colls have everything validated (is (not (m/validate [:every {:min 1000} :int] (vec (concat (range 1000) [nil]))))) From 18e7bfc4bb888fc9bc745b157f0bdea7d3079364 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 14:35:45 -0500 Subject: [PATCH 08/24] generators --- src/malli/generator.cljc | 9 +++++++++ test/malli/generator_test.cljc | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index ce3cfd3e6..6e4a288d2 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -130,6 +130,13 @@ :ex-fn #(m/-exception ::distinct-generator-failure (assoc % :schema schema))}))))) +(defn- -seqable-gen [schema options] + (gen-one-of + (-> [nil-gen] + (into (map #(-coll-gen schema % options)) + [identity vec eduction #(into-array #?(:clj Object) %)]) + (conj (-coll-distinct-gen schema set options))))) + (defn -and-gen [schema options] (if-some [gen (-not-unreachable (-> schema (m/children options) first (generator options)))] (gen/such-that (m/validator schema options) gen @@ -432,6 +439,8 @@ (defmethod -schema-generator :sequential [schema options] (-coll-gen schema identity options)) (defmethod -schema-generator :set [schema options] (-coll-distinct-gen schema set options)) (defmethod -schema-generator :enum [schema options] (gen-elements (m/children schema options))) +(defmethod -schema-generator :seqable [schema options] (-seqable-gen schema options)) +(defmethod -schema-generator :every [schema options] (-seqable-gen schema options)) ;;infinite seqs? (defmethod -schema-generator :maybe [schema options] (let [g (-> schema (m/children options) first (generator options) -not-unreachable)] diff --git a/test/malli/generator_test.cljc b/test/malli/generator_test.cljc index 7e200711d..b40ecc5af 100644 --- a/test/malli/generator_test.cljc +++ b/test/malli/generator_test.cljc @@ -1034,3 +1034,18 @@ #?(:clj Exception, :cljs js/Error) #":malli\.generator/and-generator-failure" (mg/generate [:and pos? neg?])))) + +(deftest seqable-every-generator-test + #?(:clj (is (= '[[nil ()] + ["Eduction" (0)] + ["PersistentHashSet" ()] + ["Object[]" (0)] + ["PersistentVector" (-2 2 0 1)] + ["PersistentVector" (1 -2)] + ["PersistentVector" (-9)] + ["PersistentVector" (3 -49 -4)] + ["PersistentVector" (-23 1 82)] + ["Eduction" (126 -24 -236 0 -18 0 0 2 -1)]] + (mapv (juxt #(some-> (class %) .getSimpleName) sequence) (mg/sample [:seqable :int] {:seed 0})))) + :cljs (is (= '[() (0) () (0) (-2 2 0 1) (1 -2) (-9) (3 -49 -4) (-23 1 82) (126 -24 -236 0 -18 0 0 2 -1)] + (mapv sequence (mg/sample [:seqable :int] {:seed 0})))))) From db6655116572d2482174b99f489d6a9ddfecf421 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 15:01:25 -0500 Subject: [PATCH 09/24] fix --- src/malli/generator.cljc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index 6e4a288d2..90e5b18a5 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -130,13 +130,6 @@ :ex-fn #(m/-exception ::distinct-generator-failure (assoc % :schema schema))}))))) -(defn- -seqable-gen [schema options] - (gen-one-of - (-> [nil-gen] - (into (map #(-coll-gen schema % options)) - [identity vec eduction #(into-array #?(:clj Object) %)]) - (conj (-coll-distinct-gen schema set options))))) - (defn -and-gen [schema options] (if-some [gen (-not-unreachable (-> schema (m/children options) first (generator options)))] (gen/such-that (m/validator schema options) gen @@ -150,6 +143,13 @@ (first gs) (gen/one-of gs))) +(defn- -seqable-gen [schema options] + (gen-one-of + (-> [nil-gen] + (into (map #(-coll-gen schema % options)) + [identity vec eduction #(into-array #?(:clj Object) %)]) + (conj (-coll-distinct-gen schema set options))))) + (defn -or-gen [schema options] (if-some [gs (not-empty (into [] (keep #(-not-unreachable (generator % options))) From 7bde9b10dbbdbe30f33bfbe410b7b07e9561a570 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 15:04:04 -0500 Subject: [PATCH 10/24] wip --- src/malli/core.cljc | 14 ++++++-------- test/malli/core_test.cljc | 31 +++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 6552da152..b8bd619b8 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1209,11 +1209,9 @@ (when fempty (-fail! ::cannot-provide-empty-and-bounded-props)) (-needed-bounded-checks min max options)) - validate-limits (if (or min max) - (if bounded - (-validate-bounded-limits (c/min bounded (or max bounded)) min max) - (-validate-limits min max)) - any?) + validate-limits (if bounded + (-validate-bounded-limits (c/min bounded (or max bounded)) min max) + (-validate-limits min max)) ->parser (fn [f g] (let [child-parser (f schema)] (fn [x] (cond @@ -1222,10 +1220,10 @@ :else (if bounded (let [child-validator child-parser] (reduce - (fn [acc v] - (if (child-validator v) (reduced ::invalid) acc)) + (fn [x v] + (if (child-validator v) x (reduced ::invalid))) x (cond->> x - (or (counted? x) (indexed? x)) + (not (or (counted? x) (indexed? x))) (eduction (take bounded))))) (let [x' (reduce (fn [acc v] diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 4d545c869..8eedd548d 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3237,7 +3237,16 @@ (is (nil? (m/explain [:seqable :int] #{1 2 3}))) (is (not (m/validate [:seqable :int] #{1 nil 3}))) (is (= #{["should be an integer"]} - (me/humanize (m/explain [:seqable :int] #{1 nil 3}))))) + (me/humanize (m/explain [:seqable :int] #{1 nil 3})))) + + #_ + (let [original (interleave (range 10) (cycle [true false])) + parsed [[:l 0] [:r true] [:l 1] [:r false] [:l 2] [:r true] [:l 3] [:r false] [:l 4] [:r true] [:l 5] + [:r false] [:l 6] [:r true] [:l 7] [:r false] [:l 8] [:r true] [:l 9] [:r false]]] + (is (= parsed (m/parse [:seqable [:orn [:l :int] [:r :boolean]]] original))) + (is (= original (m/unparse [:seqable [:orn [:l :int] [:r :boolean]]] parsed)))) + (m/unparse [:sequential [:orn [:a :int]]] [[:a 1]]) +) (deftest every-schema-test (is (m/validate [:every :int] #{1 2 3})) @@ -3263,4 +3272,22 @@ (is (= (concat (repeat 1000 nil) [["should be an integer"]]) (me/humanize (m/explain [:every {:min 1001} :int] (concat (range 1000) [nil]))))) (is (= (concat (repeat 1000 nil) [["should be an integer"]]) - (me/humanize (m/explain [:every {:max 1001} :int] (concat (range 1000) [nil])))))) + (me/humanize (m/explain [:every {:max 1001} :int] (concat (range 1000) [nil]))))) + (doseq [parse [#'m/parse #'m/unparse]] + (testing parse + (let [good-sequence (interleave (range 10) (cycle [true false]))] + (is (identical? good-sequence + (parse [:every [:orn [:l :int] [:r :boolean]]] + good-sequence)))) + (is (= ::m/invalid + (parse [:every [:orn [:l :int] [:r :boolean]]] + (interleave (range 10) (cycle [true false nil]))))) + (let [bad-long-seq (concat (interleave (range 1000) (cycle [true false])) + [nil]) + bad-indexed-seq (vec bad-long-seq)] + (is (identical? bad-long-seq + (parse [:every [:orn [:l :int] [:r :boolean]]] + bad-long-seq))) + (is (= ::m/invalid + (parse [:every [:orn [:l :int] [:r :boolean]]] + bad-indexed-seq))))))) From 17b3bbb6163e058af59326510c99db2b855992d1 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 15:12:27 -0500 Subject: [PATCH 11/24] fix parsing --- README.md | 4 +++- test/malli/core_test.cljc | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6785fe298..c50de2ad9 100644 --- a/README.md +++ b/README.md @@ -382,7 +382,9 @@ default branching can be arbitrarily nested: The `:seqable` and `:every` schemas describe `seqable?` collections. They differ in their handling of `counted?` or `indexed?` collections and their -[parsers](#parsing-values) (`:seqable` parses its elements but `:every?` does not). +[parsers](#parsing-values) (`:seqable` parses its elements but `:every?` does not, +and valid unparsed `:seqable` values lose the original collection type while `:every?` +returns the identical input). `:seqable` validates the entire collection, while `:every` checks only the largest of `:min`, `(inc :max)`, and `(:coll-check-limit options 101)`, or diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 8eedd548d..cbe37f601 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3238,15 +3238,18 @@ (is (not (m/validate [:seqable :int] #{1 nil 3}))) (is (= #{["should be an integer"]} (me/humanize (m/explain [:seqable :int] #{1 nil 3})))) - - #_ (let [original (interleave (range 10) (cycle [true false])) - parsed [[:l 0] [:r true] [:l 1] [:r false] [:l 2] [:r true] [:l 3] [:r false] [:l 4] [:r true] [:l 5] - [:r false] [:l 6] [:r true] [:l 7] [:r false] [:l 8] [:r true] [:l 9] [:r false]]] - (is (= parsed (m/parse [:seqable [:orn [:l :int] [:r :boolean]]] original))) - (is (= original (m/unparse [:seqable [:orn [:l :int] [:r :boolean]]] parsed)))) - (m/unparse [:sequential [:orn [:a :int]]] [[:a 1]]) -) + parsed (m/parse [:seqable [:orn [:l :int] [:r :boolean]]] original) + unparsed (m/unparse [:seqable [:orn [:l :int] [:r :boolean]]] parsed)] + (is (= original unparsed)) + (is (= [[:l 0] [:r true] [:l 1] [:r false] [:l 2] [:r true] [:l 3] [:r false] [:l 4] [:r true] [:l 5] + [:r false] [:l 6] [:r true] [:l 7] [:r false] [:l 8] [:r true] [:l 9] [:r false]] + parsed))) + (let [original (sorted-set 1 2 3) + parsed (m/parse [:seqable [:orn [:a :int]]] original) + unparsed (m/unparse [:seqable [:orn [:a :int]]] parsed)] + (is (= unparsed [1 2 3])) + (is (= parsed [[:a 1] [:a 2] [:a 3]])))) (deftest every-schema-test (is (m/validate [:every :int] #{1 2 3})) From ff02a9a7c9d80b2ae72ab0a96628d197352bc2a7 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 15:15:33 -0500 Subject: [PATCH 12/24] readme --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c50de2ad9..f53a38490 100644 --- a/README.md +++ b/README.md @@ -381,10 +381,11 @@ default branching can be arbitrarily nested: ## Seqable schemas The `:seqable` and `:every` schemas describe `seqable?` collections. They -differ in their handling of `counted?` or `indexed?` collections and their -[parsers](#parsing-values) (`:seqable` parses its elements but `:every?` does not, -and valid unparsed `:seqable` values lose the original collection type while `:every?` -returns the identical input). +differ in their handling of collections that are neither `counted?` nor `indexed?`, and their +[parsers](#parsing-values): +1. `:seqable` parses its elements but `:every?` does not and returns the identical input, and +2. valid unparsed `:seqable` values lose the original collection type while `:every?` + returns the identical input. `:seqable` validates the entire collection, while `:every` checks only the largest of `:min`, `(inc :max)`, and `(:coll-check-limit options 101)`, or From 97838338ff73de8eebefdd9e4d98aa8d0d81573d Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 15:24:04 -0500 Subject: [PATCH 13/24] generate maps --- src/malli/generator.cljc | 16 +++++++++++----- test/malli/generator_test.cljc | 32 +++++++++++++++++++------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index 90e5b18a5..6a3d25afd 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -144,11 +144,17 @@ (gen/one-of gs))) (defn- -seqable-gen [schema options] - (gen-one-of - (-> [nil-gen] - (into (map #(-coll-gen schema % options)) - [identity vec eduction #(into-array #?(:clj Object) %)]) - (conj (-coll-distinct-gen schema set options))))) + (let [el (-> schema m/children first)] + (gen-one-of + (-> [nil-gen] + (into (map #(-coll-gen schema % options)) + [identity vec eduction #(into-array #?(:clj Object) %)]) + (conj (-coll-distinct-gen schema set options)) + (cond-> + (and (= :tuple (m/type el)) + (= 2 (count (m/children el)))) + (conj (let [[k v] (m/children el)] + (generator [:map-of (or (m/properties schema) {}) k v] options)))))))) (defn -or-gen [schema options] (if-some [gs (not-empty diff --git a/test/malli/generator_test.cljc b/test/malli/generator_test.cljc index b40ecc5af..7b84ecd89 100644 --- a/test/malli/generator_test.cljc +++ b/test/malli/generator_test.cljc @@ -1036,16 +1036,22 @@ (mg/generate [:and pos? neg?])))) (deftest seqable-every-generator-test - #?(:clj (is (= '[[nil ()] - ["Eduction" (0)] - ["PersistentHashSet" ()] - ["Object[]" (0)] - ["PersistentVector" (-2 2 0 1)] - ["PersistentVector" (1 -2)] - ["PersistentVector" (-9)] - ["PersistentVector" (3 -49 -4)] - ["PersistentVector" (-23 1 82)] - ["Eduction" (126 -24 -236 0 -18 0 0 2 -1)]] - (mapv (juxt #(some-> (class %) .getSimpleName) sequence) (mg/sample [:seqable :int] {:seed 0})))) - :cljs (is (= '[() (0) () (0) (-2 2 0 1) (1 -2) (-9) (3 -49 -4) (-23 1 82) (126 -24 -236 0 -18 0 0 2 -1)] - (mapv sequence (mg/sample [:seqable :int] {:seed 0})))))) + (doseq [op [:seqable :every]] + (testing op + #?(:clj (is (= '[[nil ()] + ["Eduction" (0)] + ["PersistentHashSet" ()] + ["Object[]" (0)] + ["PersistentVector" (-2 2 0 1)] + ["PersistentVector" (1 -2)] + ["PersistentVector" (-9)] + ["PersistentVector" (3 -49 -4)] + ["PersistentVector" (-23 1 82)] + ["Eduction" (126 -24 -236 0 -18 0 0 2 -1)]] + (mapv (juxt #(some-> (class %) .getSimpleName) sequence) (mg/sample [op :int] {:seed 0})))) + :cljs (is (= '[() (0) () (0) (-2 2 0 1) (1 -2) (-9) (3 -49 -4) (-23 1 82) (126 -24 -236 0 -18 0 0 2 -1)] + (mapv sequence (mg/sample [op :int] {:seed 0}))))) + (is (= '({-1 false} + {-4399 true, 59 false, -4049 false, -49 false, -1 false, 15 false, -967 false, -3 false, -674 false, 2730 true, -2104 false, 3 false, -444 true, 8 false} + {119 true, 1324 false, 7276 false, -2410 true}) + (filter map? (mg/sample [op [:tuple :int :boolean]] {:seed 1 :size 30}))))))) From 70ab4f5035fae17f52de8dbd76f80738d55b4370 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 15:41:12 -0500 Subject: [PATCH 14/24] refactor --- src/malli/core.cljc | 17 ++++++++++++++--- test/malli/core_test.cljc | 2 ++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index b8bd619b8..eb64ec89c 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1184,6 +1184,17 @@ (-get [_ key default] (get children key default)) (-set [this key value] (-set-assoc-children this key value)))))))) +(defn- -check-entire-bounded-collection? [x] + (or (nil? x) + (counted? x) + (indexed? x) + ;; note: js/Object not ISeqable + #?(:clj (instance? java.util.Map x)) + #?(:clj (instance? CharSequence x) + :cljs (string? x)) + #?(:clj (.isArray (class x)) + :cljs (identical? js/Array (type x))))) + (defn -collection-schema [props] (if (fn? props) (do (-deprecated! "-collection-schema doesn't take fn-props, use :compiled property instead") @@ -1223,7 +1234,7 @@ (fn [x v] (if (child-validator v) x (reduced ::invalid))) x (cond->> x - (not (or (counted? x) (indexed? x))) + (not (-check-entire-bounded-collection? x)) (eduction (take bounded))))) (let [x' (reduce (fn [acc v] @@ -1246,7 +1257,7 @@ (validate-limits x) (reduce (fn [acc v] (if (validator v) acc (reduced false))) true (cond->> x - (and bounded (not (counted? x)) (not (indexed? x))) + (and bounded (not (-check-entire-bounded-collection? x))) (eduction (take bounded)))))))) (-explainer [this path] (let [explainer (-explainer schema (conj path 0))] @@ -1254,7 +1265,7 @@ (cond (not (fpred x)) (conj acc (miu/-error path in this x ::invalid-type)) (not (validate-limits x)) (conj acc (miu/-error path in this x ::limits)) - :else (let [size (if (and bounded (not (counted? x)) (not (indexed? x))) + :else (let [size (if (and bounded (not (-check-entire-bounded-collection? x))) bounded (count x))] (loop [acc acc, i 0, [x & xs] x] diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index cbe37f601..c615fe919 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3226,6 +3226,7 @@ {:registry registry, ::m/ref-key :id})))))))) (deftest seqable-schema-test + (is (m/validate [:seqable :int] nil)) (is (m/validate [:seqable :int] #{1 2 3})) (is (m/validate [:seqable :int] [1 2 3])) (is (m/validate [:seqable :int] (sorted-set 1 2 3))) @@ -3252,6 +3253,7 @@ (is (= parsed [[:a 1] [:a 2] [:a 3]])))) (deftest every-schema-test + (is (m/validate [:every :int] nil)) (is (m/validate [:every :int] #{1 2 3})) (is (m/validate [:every :int] [1 2 3])) (is (m/validate [:every :int] (sorted-set 1 2 3))) From bebea74962c8d469eb864889ecc277300e43208f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 15:49:48 -0500 Subject: [PATCH 15/24] more classes --- src/malli/core.cljc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index eb64ec89c..1eca1601d 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1190,6 +1190,9 @@ (indexed? x) ;; note: js/Object not ISeqable #?(:clj (instance? java.util.Map x)) + ;; many Seq's are List's, so just pick some popular classes + #?(:clj (instance? java.util.AbstractList x)) + #?(:clj (instance? java.util.Vector x)) #?(:clj (instance? CharSequence x) :cljs (string? x)) #?(:clj (.isArray (class x)) From de2364b581a307ce916383ab3efbf277db8604b6 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 16:01:10 -0500 Subject: [PATCH 16/24] wip --- test/malli/core_test.cljc | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index c615fe919..e056d146e 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3233,8 +3233,11 @@ (is (m/validate [:seqable :int] #{1 2 3})) (is (m/validate [:seqable :int] (range 1000))) (is (not (m/validate [:seqable :int] (conj (vec (range 1000)) nil)))) - (is (not (m/validate [:seqable :int] (concat (range 1000) [nil])))) - (is (not (m/validate [:seqable {:min 1000} :int] (concat (range 1000) [nil])))) + (is (not (m/validate [:seqable :int] (eduction (concat (range 1000) [nil]))))) + ;;FIXME need to handle eductions better, they don't support .count(). should count + ;; them as we check elements so we don't recompute each element. + #_ + (is (not (m/validate [:seqable {:min 1000} :int] (eduction (concat (range 1000) [nil]))))) (is (nil? (m/explain [:seqable :int] #{1 2 3}))) (is (not (m/validate [:seqable :int] #{1 nil 3}))) (is (= #{["should be an integer"]} @@ -3263,21 +3266,21 @@ (is (m/validate [:every :int] (concat (range 1000) [nil]))) ;; counted/indexed colls have everything validated (is (not (m/validate [:every :int] (vec (concat (range 1000) [nil]))))) - (is (m/validate [:every :int] (concat (range 1000) [nil]))) - (is (m/validate [:every {:min 1000} :int] (concat (range 1000) [nil]))) + (is (m/validate [:every :int] (eduction (concat (range 1000) [nil])))) + (is (m/validate [:every {:min 1000} :int] (eduction (concat (range 1000) [nil])))) ;; counted/indexed colls have everything validated (is (not (m/validate [:every {:min 1000} :int] (vec (concat (range 1000) [nil]))))) - (is (not (m/validate [:every {:min 1001} :int] (concat (range 1000) [nil])))) + (is (not (m/validate [:every {:min 1001} :int] (eduction (concat (range 1000) [nil]))))) (is (m/validate [:every {:max 1000} :int] (range 1000))) (is (not (m/validate [:every {:max 1000} :int] (range 1001)))) - (is (not (m/validate [:every {:max 1001} :int] (concat (range 1000) [nil])))) + (is (not (m/validate [:every {:max 1001} :int] (eduction (concat (range 1000) [nil]))))) (is (= #{["should be an integer"]} (me/humanize (m/explain [:every :int] #{1 nil 3})))) - (is (nil? (m/explain [:every :int] (concat (range 1000) [nil])))) + (is (nil? (m/explain [:every :int] (eduction (concat (range 1000) [nil]))))) (is (= (concat (repeat 1000 nil) [["should be an integer"]]) - (me/humanize (m/explain [:every {:min 1001} :int] (concat (range 1000) [nil]))))) + (me/humanize (m/explain [:every {:min 1001} :int] (eduction (concat (range 1000) [nil])))))) (is (= (concat (repeat 1000 nil) [["should be an integer"]]) - (me/humanize (m/explain [:every {:max 1001} :int] (concat (range 1000) [nil]))))) + (me/humanize (m/explain [:every {:max 1001} :int] (eduction (concat (range 1000) [nil])))))) (doseq [parse [#'m/parse #'m/unparse]] (testing parse (let [good-sequence (interleave (range 10) (cycle [true false]))] @@ -3287,12 +3290,13 @@ (is (= ::m/invalid (parse [:every [:orn [:l :int] [:r :boolean]]] (interleave (range 10) (cycle [true false nil]))))) - (let [bad-long-seq (concat (interleave (range 1000) (cycle [true false])) - [nil]) - bad-indexed-seq (vec bad-long-seq)] - (is (identical? bad-long-seq + (let [bad-but-too-big (eduction + (concat (interleave (range 1000) (cycle [true false])) + [nil])) + bad-indexed-seq (vec bad-but-too-big)] + (is (identical? bad-but-too-big (parse [:every [:orn [:l :int] [:r :boolean]]] - bad-long-seq))) + bad-but-too-big))) (is (= ::m/invalid (parse [:every [:orn [:l :int] [:r :boolean]]] bad-indexed-seq))))))) From d22195960c1e29bf2135220e6f2cd7a4c53dd12f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 16:17:38 -0500 Subject: [PATCH 17/24] add back lazyseq tests --- test/malli/core_test.cljc | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index e056d146e..db7955528 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3233,11 +3233,13 @@ (is (m/validate [:seqable :int] #{1 2 3})) (is (m/validate [:seqable :int] (range 1000))) (is (not (m/validate [:seqable :int] (conj (vec (range 1000)) nil)))) + (is (not (m/validate [:seqable :int] (concat (range 1000) [nil])))) (is (not (m/validate [:seqable :int] (eduction (concat (range 1000) [nil]))))) ;;FIXME need to handle eductions better, they don't support .count(). should count ;; them as we check elements so we don't recompute each element. #_ (is (not (m/validate [:seqable {:min 1000} :int] (eduction (concat (range 1000) [nil]))))) + (is (not (m/validate [:seqable {:min 1000} :int] (concat (range 1000) [nil])))) (is (nil? (m/explain [:seqable :int] #{1 2 3}))) (is (not (m/validate [:seqable :int] #{1 nil 3}))) (is (= #{["should be an integer"]} @@ -3264,21 +3266,31 @@ (is (nil? (m/explain [:every :int] #{1 2 3}))) (is (not (m/validate [:every :int] #{1 nil 3}))) (is (m/validate [:every :int] (concat (range 1000) [nil]))) + (is (m/validate [:every :int] (eduction (concat (range 1000) [nil])))) ;; counted/indexed colls have everything validated (is (not (m/validate [:every :int] (vec (concat (range 1000) [nil]))))) + (is (m/validate [:every :int] (concat (range 1000) [nil]))) (is (m/validate [:every :int] (eduction (concat (range 1000) [nil])))) + (is (m/validate [:every {:min 1000} :int] (concat (range 1000) [nil]))) (is (m/validate [:every {:min 1000} :int] (eduction (concat (range 1000) [nil])))) ;; counted/indexed colls have everything validated (is (not (m/validate [:every {:min 1000} :int] (vec (concat (range 1000) [nil]))))) + (is (not (m/validate [:every {:min 1001} :int] (concat (range 1000) [nil])))) (is (not (m/validate [:every {:min 1001} :int] (eduction (concat (range 1000) [nil]))))) (is (m/validate [:every {:max 1000} :int] (range 1000))) (is (not (m/validate [:every {:max 1000} :int] (range 1001)))) + (is (not (m/validate [:every {:max 1001} :int] (concat (range 1000) [nil])))) (is (not (m/validate [:every {:max 1001} :int] (eduction (concat (range 1000) [nil]))))) (is (= #{["should be an integer"]} (me/humanize (m/explain [:every :int] #{1 nil 3})))) + (is (nil? (m/explain [:every :int] (concat (range 1000) [nil])))) (is (nil? (m/explain [:every :int] (eduction (concat (range 1000) [nil]))))) + (is (= (concat (repeat 1000 nil) [["should be an integer"]]) + (me/humanize (m/explain [:every {:min 1001} :int] (concat (range 1000) [nil]))))) (is (= (concat (repeat 1000 nil) [["should be an integer"]]) (me/humanize (m/explain [:every {:min 1001} :int] (eduction (concat (range 1000) [nil])))))) + (is (= (concat (repeat 1000 nil) [["should be an integer"]]) + (me/humanize (m/explain [:every {:max 1001} :int] (concat (range 1000) [nil]))))) (is (= (concat (repeat 1000 nil) [["should be an integer"]]) (me/humanize (m/explain [:every {:max 1001} :int] (eduction (concat (range 1000) [nil])))))) (doseq [parse [#'m/parse #'m/unparse]] @@ -3290,13 +3302,15 @@ (is (= ::m/invalid (parse [:every [:orn [:l :int] [:r :boolean]]] (interleave (range 10) (cycle [true false nil]))))) - (let [bad-but-too-big (eduction - (concat (interleave (range 1000) (cycle [true false])) - [nil])) - bad-indexed-seq (vec bad-but-too-big)] - (is (identical? bad-but-too-big - (parse [:every [:orn [:l :int] [:r :boolean]]] - bad-but-too-big))) - (is (= ::m/invalid - (parse [:every [:orn [:l :int] [:r :boolean]]] - bad-indexed-seq))))))) + (doseq [coerce [#'identity #'eduction]] + (testing coerce + (let [bad-but-too-big (coerce + (concat (interleave (range 1000) (cycle [true false])) + [nil])) + bad-indexed-seq (vec bad-but-too-big)] + (is (identical? bad-but-too-big + (parse [:every [:orn [:l :int] [:r :boolean]]] + bad-but-too-big))) + (is (= ::m/invalid + (parse [:every [:orn [:l :int] [:r :boolean]]] + bad-indexed-seq))))))))) From a70c9967abebd2b5f71a5603bc19a6be2c987921 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 30 Apr 2024 14:42:12 -0500 Subject: [PATCH 18/24] fix bb --- src/malli/core.cljc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 368757b7b..f951ff300 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1191,8 +1191,9 @@ ;; note: js/Object not ISeqable #?(:clj (instance? java.util.Map x)) ;; many Seq's are List's, so just pick some popular classes - #?(:clj (instance? java.util.AbstractList x)) - #?(:clj (instance? java.util.Vector x)) + #?@(:bb [] + :clj [(instance? java.util.AbstractList x) + (instance? java.util.Vector x)]) #?(:clj (instance? CharSequence x) :cljs (string? x)) #?(:clj (.isArray (class x)) From 0a214434df210f672b3845482959384cd3900762 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 1 May 2024 12:02:25 -0500 Subject: [PATCH 19/24] cljs --- test/malli/core_test.cljc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 82e7cc704..a563d9234 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3302,7 +3302,9 @@ (is (= ::m/invalid (parse [:every [:orn [:l :int] [:r :boolean]]] (interleave (range 10) (cycle [true false nil]))))) - (doseq [coerce [#'identity #'eduction]] + (doseq [coerce [#'identity #?(:clj #'eduction + ;;TODO :cljs ? + )]] (testing coerce (let [bad-but-too-big (coerce (concat (interleave (range 1000) (cycle [true false])) From 088df5dea10c6ebeedf859b83ad52dab55d610b1 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 16 Jul 2024 00:19:54 -0500 Subject: [PATCH 20/24] dbg --- src/malli/core.cljc | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index c96daad73..f62eb2ea3 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1187,19 +1187,22 @@ (-set [this key value] (-set-assoc-children this key value)))))))) (defn- -check-entire-bounded-collection? [x] - (or (nil? x) - (counted? x) - (indexed? x) - ;; note: js/Object not ISeqable - #?(:clj (instance? java.util.Map x)) - ;; many Seq's are List's, so just pick some popular classes - #?@(:bb [] - :clj [(instance? java.util.AbstractList x) - (instance? java.util.Vector x)]) - #?(:clj (instance? CharSequence x) - :cljs (string? x)) - #?(:clj (.isArray (class x)) - :cljs (identical? js/Array (type x))))) + (let [res (or (nil? x) + (counted? x) + (indexed? x) + ;; note: js/Object not ISeqable + #?(:clj (instance? java.util.Map x)) + ;; many Seq's are List's, so just pick some popular classes + #?@(:bb [] + :clj [(instance? java.util.AbstractList x) + (instance? java.util.Vector x)]) + #?(:clj (instance? CharSequence x) + :cljs (string? x)) + #?(:clj (.isArray (class x)) + :cljs (identical? js/Array (type x))))] + (prn "-check-entire-bounded-collection?" (type x) res) + res + )) (defn -collection-schema [props] (if (fn? props) From d29040d2c1004e53e837bd1676d7f245ae542053 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 16 Jul 2024 00:22:12 -0500 Subject: [PATCH 21/24] fix --- src/malli/core.cljc | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index f62eb2ea3..1aed50106 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1187,22 +1187,19 @@ (-set [this key value] (-set-assoc-children this key value)))))))) (defn- -check-entire-bounded-collection? [x] - (let [res (or (nil? x) - (counted? x) - (indexed? x) - ;; note: js/Object not ISeqable - #?(:clj (instance? java.util.Map x)) - ;; many Seq's are List's, so just pick some popular classes - #?@(:bb [] - :clj [(instance? java.util.AbstractList x) - (instance? java.util.Vector x)]) - #?(:clj (instance? CharSequence x) - :cljs (string? x)) - #?(:clj (.isArray (class x)) - :cljs (identical? js/Array (type x))))] - (prn "-check-entire-bounded-collection?" (type x) res) - res - )) + (or (nil? x) + (counted? x) + (indexed? x) + ;; note: js/Object not ISeqable + #?(:clj (instance? java.util.Map x)) + ;; many Seq's are List's, so just pick some popular classes + #?@(:bb [] + :clj [(instance? java.util.AbstractList x) + (instance? java.util.Vector x)]) + #?(:clj (instance? CharSequence x) + :cljs (string? x)) + #?(:clj (.isArray (class x)) + :cljs (identical? js/Array (c/type x))))) (defn -collection-schema [props] (if (fn? props) From 8fd89b4772c610d7ea7b20e10def5fc362e73ec6 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 19 Jul 2024 17:45:22 +0300 Subject: [PATCH 22/24] format --- src/malli/core.cljc | 28 ++++++++++++++-------------- src/malli/generator.cljc | 18 +++++++++--------- test/malli/core_test.cljc | 3 +-- test/malli/generator_test.cljc | 28 ++++++++++++++-------------- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 1aed50106..297cab0b8 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -1193,12 +1193,12 @@ ;; note: js/Object not ISeqable #?(:clj (instance? java.util.Map x)) ;; many Seq's are List's, so just pick some popular classes - #?@(:bb [] - :clj [(instance? java.util.AbstractList x) - (instance? java.util.Vector x)]) - #?(:clj (instance? CharSequence x) + #?@(:bb [] + :clj [(instance? java.util.AbstractList x) + (instance? java.util.Vector x)]) + #?(:clj (instance? CharSequence x) :cljs (string? x)) - #?(:clj (.isArray (class x)) + #?(:clj (.isArray (class x)) :cljs (identical? js/Array (c/type x))))) (defn -collection-schema [props] @@ -1237,16 +1237,16 @@ :else (if bounded (let [child-validator child-parser] (reduce - (fn [x v] - (if (child-validator v) x (reduced ::invalid))) - x (cond->> x - (not (-check-entire-bounded-collection? x)) - (eduction (take bounded))))) + (fn [x v] + (if (child-validator v) x (reduced ::invalid))) + x (cond->> x + (not (-check-entire-bounded-collection? x)) + (eduction (take bounded))))) (let [x' (reduce - (fn [acc v] - (let [v' (child-parser v)] - (if (miu/-invalid? v') (reduced ::invalid) (conj acc v')))) - [] x)] + (fn [acc v] + (let [v' (child-parser v)] + (if (miu/-invalid? v') (reduced ::invalid) (conj acc v')))) + [] x)] (cond (miu/-invalid? x') x' g (g x') diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index 7c4d59440..bd9a95330 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -146,15 +146,15 @@ (defn- -seqable-gen [schema options] (let [el (-> schema m/children first)] (gen-one-of - (-> [nil-gen] - (into (map #(-coll-gen schema % options)) - [identity vec eduction #(into-array #?(:clj Object) %)]) - (conj (-coll-distinct-gen schema set options)) - (cond-> - (and (= :tuple (m/type el)) - (= 2 (count (m/children el)))) - (conj (let [[k v] (m/children el)] - (generator [:map-of (or (m/properties schema) {}) k v] options)))))))) + (-> [nil-gen] + (into (map #(-coll-gen schema % options)) + [identity vec eduction #(into-array #?(:clj Object) %)]) + (conj (-coll-distinct-gen schema set options)) + (cond-> + (and (= :tuple (m/type el)) + (= 2 (count (m/children el)))) + (conj (let [[k v] (m/children el)] + (generator [:map-of (or (m/properties schema) {}) k v] options)))))))) (defn -or-gen [schema options] (if-some [gs (not-empty diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 9af39a75f..8f51c5021 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3370,8 +3370,7 @@ (is (not (m/validate [:seqable :int] (eduction (concat (range 1000) [nil]))))) ;;FIXME need to handle eductions better, they don't support .count(). should count ;; them as we check elements so we don't recompute each element. - #_ - (is (not (m/validate [:seqable {:min 1000} :int] (eduction (concat (range 1000) [nil]))))) + #_(is (not (m/validate [:seqable {:min 1000} :int] (eduction (concat (range 1000) [nil]))))) (is (not (m/validate [:seqable {:min 1000} :int] (concat (range 1000) [nil])))) (is (nil? (m/explain [:seqable :int] #{1 2 3}))) (is (not (m/validate [:seqable :int] #{1 nil 3}))) diff --git a/test/malli/generator_test.cljc b/test/malli/generator_test.cljc index c71010532..5fdcbc5e5 100644 --- a/test/malli/generator_test.cljc +++ b/test/malli/generator_test.cljc @@ -1067,24 +1067,24 @@ #":malli\.generator/distinct-generator-failure" (mg/generate [:map-of {:min 2} [:= 1] :any]))) (is (thrown-with-msg? - #?(:clj Exception, :cljs js/Error) - #":malli\.generator/and-generator-failure" - (mg/generate [:and pos? neg?])))) + #?(:clj Exception, :cljs js/Error) + #":malli\.generator/and-generator-failure" + (mg/generate [:and pos? neg?])))) (deftest seqable-every-generator-test (doseq [op [:seqable :every]] (testing op - #?(:clj (is (= '[[nil ()] - ["Eduction" (0)] - ["PersistentHashSet" ()] - ["Object[]" (0)] - ["PersistentVector" (-2 2 0 1)] - ["PersistentVector" (1 -2)] - ["PersistentVector" (-9)] - ["PersistentVector" (3 -49 -4)] - ["PersistentVector" (-23 1 82)] - ["Eduction" (126 -24 -236 0 -18 0 0 2 -1)]] - (mapv (juxt #(some-> (class %) .getSimpleName) sequence) (mg/sample [op :int] {:seed 0})))) + #?(:clj (is (= '[[nil ()] + ["Eduction" (0)] + ["PersistentHashSet" ()] + ["Object[]" (0)] + ["PersistentVector" (-2 2 0 1)] + ["PersistentVector" (1 -2)] + ["PersistentVector" (-9)] + ["PersistentVector" (3 -49 -4)] + ["PersistentVector" (-23 1 82)] + ["Eduction" (126 -24 -236 0 -18 0 0 2 -1)]] + (mapv (juxt #(some-> (class %) .getSimpleName) sequence) (mg/sample [op :int] {:seed 0})))) :cljs (is (= '[() (0) () (0) (-2 2 0 1) (1 -2) (-9) (3 -49 -4) (-23 1 82) (126 -24 -236 0 -18 0 0 2 -1)] (mapv sequence (mg/sample [op :int] {:seed 0}))))) (is (= '({-1 false} From 0b2c7054c70e6282a8a772d2b87734c8bba326b1 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 19 Jul 2024 17:46:51 +0300 Subject: [PATCH 23/24] review comments --- README.md | 6 +++--- src/malli/core.cljc | 2 +- test/malli/core_test.cljc | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ed00167a1..1e0f6b570 100644 --- a/README.md +++ b/README.md @@ -388,12 +388,12 @@ default branching can be arbitrarily nested: The `:seqable` and `:every` schemas describe `seqable?` collections. They differ in their handling of collections that are neither `counted?` nor `indexed?`, and their [parsers](#parsing-values): -1. `:seqable` parses its elements but `:every?` does not and returns the identical input, and -2. valid unparsed `:seqable` values lose the original collection type while `:every?` +1. `:seqable` parses its elements but `:every` does not and returns the identical input, and +2. valid unparsed `:seqable` values lose the original collection type while `:every` returns the identical input. `:seqable` validates the entire collection, while `:every` checks only the -largest of `:min`, `(inc :max)`, and `(:coll-check-limit options 101)`, or +largest of `:min`, `(inc :max)`, and `(::m/coll-check-limit options 101)`, or the entire collection if the input is `counted?` or `indexed?`. ```clojure diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 297cab0b8..33475bbbc 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -644,7 +644,7 @@ (defn -needed-bounded-checks [min max options] (c/max (or (some-> max inc) 0) (or min 0) - (:coll-check-limit options 101))) + (::coll-check-limit options 101))) (defn -validate-bounded-limits [needed min max] (or ((-min-max-pred #(bounded-count needed %)) {:min min :max max}) (constantly true))) diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 8f51c5021..4c8d83966 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3404,6 +3404,7 @@ (is (m/validate [:every :int] (concat (range 1000) [nil]))) (is (m/validate [:every :int] (eduction (concat (range 1000) [nil])))) (is (m/validate [:every {:min 1000} :int] (concat (range 1000) [nil]))) + (is (m/validate [:every :int] (concat (range 1000) [nil]) {::m/coll-check-limit 1000})) (is (m/validate [:every {:min 1000} :int] (eduction (concat (range 1000) [nil])))) ;; counted/indexed colls have everything validated (is (not (m/validate [:every {:min 1000} :int] (vec (concat (range 1000) [nil]))))) @@ -3415,7 +3416,7 @@ (is (not (m/validate [:every {:max 1001} :int] (eduction (concat (range 1000) [nil]))))) (is (= #{["should be an integer"]} (me/humanize (m/explain [:every :int] #{1 nil 3})))) - (is (nil? (m/explain [:every :int] (concat (range 1000) [nil])))) + (is (nil? (m/explain [:every :int] (concat (range 100) [nil])))) (is (nil? (m/explain [:every :int] (eduction (concat (range 1000) [nil]))))) (is (= (concat (repeat 1000 nil) [["should be an integer"]]) (me/humanize (m/explain [:every {:min 1001} :int] (concat (range 1000) [nil]))))) @@ -3439,8 +3440,8 @@ )]] (testing coerce (let [bad-but-too-big (coerce - (concat (interleave (range 1000) (cycle [true false])) - [nil])) + (concat (interleave (range 1000) (cycle [true false])) + [nil])) bad-indexed-seq (vec bad-but-too-big)] (is (identical? bad-but-too-big (parse [:every [:orn [:l :int] [:r :boolean]]] @@ -3462,4 +3463,4 @@ :value {:x 1, :y "2"} :errors [{:path [::m/in :y], :in [:y], :schema y-schema, :value "2"}]} explain)) - (is (form= y-schema (mu/get-in schema (-> explain :errors first :path)))))) + (is (form= y-schema (mu/get-in schema (-> explain :errors first :path)))))) :ever From 9c26e98bd46d89bbb6b05eb72f9d161d0c01591f Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 19 Jul 2024 17:56:44 +0300 Subject: [PATCH 24/24] Update core_test.cljc fix test --- test/malli/core_test.cljc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 56f06f705..e579afb9e 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -3442,7 +3442,7 @@ (is (not (m/validate [:every {:max 1001} :int] (eduction (concat (range 1000) [nil]))))) (is (= #{["should be an integer"]} (me/humanize (m/explain [:every :int] #{1 nil 3})))) - (is (nil? (m/explain [:every :int] (concat (range 100) [nil])))) + (is (nil? (m/explain [:every :int] (concat (range 1000) [nil])))) (is (nil? (m/explain [:every :int] (eduction (concat (range 1000) [nil]))))) (is (= (concat (repeat 1000 nil) [["should be an integer"]]) (me/humanize (m/explain [:every {:min 1001} :int] (concat (range 1000) [nil]))))) @@ -3496,4 +3496,4 @@ #?(:clj Exception, :cljs js/Error) #?(:clj #":malli\.core/infinitely-expanding-schema" :cljs #":malli\.core/invalid-schema") - (m/schema [(m/schema :any)])))) \ No newline at end of file + (m/schema [(m/schema :any)]))))