diff --git a/src/malli/core.cljc b/src/malli/core.cljc index 65cae4816..2fd821ef8 100644 --- a/src/malli/core.cljc +++ b/src/malli/core.cljc @@ -824,6 +824,7 @@ (defn -int-schema [] (-simple-schema {:type :int, :pred int?, :property-pred (-min-max-pred nil)})) (defn -float-schema [] (-simple-schema {:type :float, :pred float?, :property-pred (-min-max-pred nil)})) (defn -double-schema [] (-simple-schema {:type :double, :pred double?, :property-pred (-min-max-pred nil)})) +#?(:clj (defn -decimal-schema [] (-simple-schema {:type :decimal, :pred decimal?, :property-pred (-min-max-pred nil)}))) (defn -boolean-schema [] (-simple-schema {:type :boolean, :pred boolean?})) (defn -keyword-schema [] (-simple-schema {:type :keyword, :pred keyword?})) (defn -symbol-schema [] (-simple-schema {:type :symbol, :pred symbol?})) @@ -2958,6 +2959,7 @@ :int (-int-schema) :float (-float-schema) :double (-double-schema) + #?@(:clj [:decimal (-decimal-schema)]) :boolean (-boolean-schema) :keyword (-keyword-schema) :symbol (-symbol-schema) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index 938bc749b..20ca14285 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -418,6 +418,24 @@ (defmethod -schema-generator :int [schema options] (gen/large-integer* (-min-max schema options))) (defmethod -schema-generator :double [schema options] (double-gen schema options)) (defmethod -schema-generator :float [schema options] (double-gen schema options)) +#?(:clj (defmethod -schema-generator :decimal [schema options] + (let [{:keys [min max] :as bounds} (-min-max schema options) + opts (merge bounds {:NaN? false, :infinite? false}) + safe-double? #(if % (not (Double/isInfinite (double %))) true) + use-double? (and (safe-double? min) (safe-double? max))] + (if use-double? + (gen-fmap bigdec (gen-double opts)) + (let [min-int (if min (bigint min) (if max (- (bigint max) 1000000000000000000000000N) -1000000000000000000000000N)) + max-int (if max (bigint max) (if min (+ (bigint min) 1000000000000000000000000N) 1000000000000000000000000N)) + int-gen (gen/large-integer* {:min min-int, :max max-int}) + frac-gen (gen-double {:min 0.0, :max 0.999999999, :NaN? false, :infinite? false})] + (gen-fmap (fn [[i f]] + (let [res (+ (bigdec i) (bigdec f))] + (cond + (and min (< res min)) (bigdec min) + (and max (> res max)) (bigdec max) + :else res))) + (gen-tuple [int-gen frac-gen]))))))) (defmethod -schema-generator :boolean [_ _] gen/boolean) (defmethod -schema-generator :keyword [_ _] gen/keyword) (defmethod -schema-generator :symbol [_ _] gen/symbol) diff --git a/src/malli/transform.cljc b/src/malli/transform.cljc index 79c8dc001..d4f2f1661 100644 --- a/src/malli/transform.cljc +++ b/src/malli/transform.cljc @@ -334,6 +334,7 @@ :int -string->long :float -string->float :double -string->double + #?@(:clj [:decimal -string->decimal]) :boolean -string->boolean :> -string->long @@ -369,6 +370,7 @@ :int -any->string :float -any->string :double -any->string + #?@(:clj [:decimal -any->string]) ;:boolean -any->string :> -any->string diff --git a/test/malli/core_test.cljc b/test/malli/core_test.cljc index 1f9e966f6..492e487f9 100644 --- a/test/malli/core_test.cljc +++ b/test/malli/core_test.cljc @@ -2212,6 +2212,24 @@ [1.1 3.1 mt/string-transformer [:double {:encode/string {:enter inc, :leave inc}}]]] :ast {:type :double, :properties {:min 1.0, :max 4.0}} :form [:double {:min 1.0, :max 4.0}]} + #?@(:clj [:decimal {:schema [:decimal {:min 1.0M, :max 4.0M}] + :validate {:success [1.0M 2.2M 4.0M] + :failure [nil "invalid" 0.5M 1.0]} + :explain [[1.0M] + [false {:schema [:decimal {:min 1.0M, :max 4.0M}] + :value false + :errors [{:path [] + :in [] + :schema [:decimal {:min 1.0M, :max 4.0M}] + :value false}]}]] + :decode [["1.1" 1.1M mt/string-transformer] + ["1.1" "1.1" mt/json-transformer] + [1.1M 3.1M mt/string-transformer [:decimal {:decode/string {:enter inc, :leave inc}}]]] + :encode [[1.1M "1.1" mt/string-transformer] + [1.1M 1.1M mt/json-transformer] + [1.1M 3.1M mt/string-transformer [:decimal {:encode/string {:enter inc, :leave inc}}]]] + :ast {:type :decimal, :properties {:min 1.0M, :max 4.0M}} + :form [:decimal {:min 1.0M, :max 4.0M}]}]) :keyword {:schema :keyword :validate {:success [:abba :user/abba] :failure [nil "invalid"]} diff --git a/test/malli/generator_test.cljc b/test/malli/generator_test.cljc index d797c02d1..ec2b129a1 100644 --- a/test/malli/generator_test.cljc +++ b/test/malli/generator_test.cljc @@ -345,6 +345,22 @@ [:set {:min 1, :gen/min 10, :max 100, :gen/max 200} int?] [:string {:min 1, :gen/min 10, :max 100, :gen/max 200}]))) +#?(:clj + (deftest decimal-generator-test + (testing "basic generation" + (is (every? decimal? (mg/sample [:decimal {:min 0.0M, :max 1.0M}] {:size 100}))) + (is (every? #(<= 0.0M % 1.0M) (mg/sample [:decimal {:min 0.0M, :max 1.0M}] {:size 100})))) + (testing "unbounded generation" + (is (every? decimal? (mg/sample :decimal {:size 100})))) + (testing "gen/min and gen/max properties" + (is (every? #(<= 10.0M % 20.0M) (mg/sample [:decimal {:min 0.0M, :max 100.0M, :gen/min 10.0M, :gen/max 20.0M}] {:size 100})))) + (testing "mixed type bounds" + (is (every? #(<= 0.0M % 1.0M) (mg/sample [:decimal {:min 0, :max 1.0}] {:size 100})))) + (testing "huge bounds beyond double range" + (is (every? #(<= 1e400M % 2e400M) (mg/sample [:decimal {:min 1e400M, :max 2e400M}] {:size 100}))) + (is (every? #(<= 1e400M %) (mg/sample [:decimal {:min 1e400M}] {:size 100}))) + (is (every? #(>= -1e400M %) (mg/sample [:decimal {:max -1e400M}] {:size 100})))))) + (deftest protocol-test (let [values #{1 2 3 5 8 13} schema (reify