Skip to content

Commit f63e9bd

Browse files
committed
predicates: introduce coercions
1 parent 262f255 commit f63e9bd

File tree

5 files changed

+125
-4
lines changed

5 files changed

+125
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Utilities for [clojure.spec](https://github.com/clojure/spec.alpha).
55
## Installation
66

77
```clojure
8-
[com.nedap.staffing-solutions/utils.spec "0.6.0"]
8+
[com.nedap.staffing-solutions/utils.spec "0.6.1"]
99
````
1010

1111
## ns organisation

project.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(defproject com.nedap.staffing-solutions/utils.spec "0.6.0"
1+
(defproject com.nedap.staffing-solutions/utils.spec "0.6.1"
22
:description "clojure.spec utilities"
33
:url "https://github.com/nedap/utils.spec"
44
:license {:name "Eclipse Public License"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
(ns nedap.utils.spec.impl.predicates)
2+
3+
(defn coercer [pred]
4+
(fn [x]
5+
(cond
6+
(pred x) x
7+
(not (string? x)) x
8+
:else (try
9+
(let [v (BigInteger. ^String x)]
10+
(if-not (pred v)
11+
x
12+
(if (<= Long/MIN_VALUE v Long/MAX_VALUE)
13+
(Long/parseLong x)
14+
v)))
15+
(catch Exception _
16+
x)))))

src/nedap/utils/spec/predicates.clj

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
(ns nedap.utils.spec.predicates
22
(:require
3-
[nedap.utils.speced :as speced]))
3+
[nedap.utils.spec.impl.predicates :as impl]
4+
[nedap.utils.speced :as speced]
5+
[spec-coerce.core :as spec-coerce]))
46

57
(speced/defn ^Boolean neg-integer?
68
"Is `x` negative (as per `clojure.core/neg?`) and integer (as per `clojure.core/integer?`)?
@@ -32,3 +34,15 @@
3234
(or (string? x)
3335
(symbol? x)
3436
(keyword? x)))
37+
38+
(def neg-integer-coercer (impl/coercer neg-integer?))
39+
40+
(def nat-integer-coercer (impl/coercer nat-integer?))
41+
42+
(def pos-integer-coercer (impl/coercer pos-integer?))
43+
44+
(defmethod spec-coerce/sym->coercer `neg-integer? [_] neg-integer-coercer)
45+
46+
(defmethod spec-coerce/sym->coercer `nat-integer? [_] nat-integer-coercer)
47+
48+
(defmethod spec-coerce/sym->coercer `pos-integer? [_] pos-integer-coercer)

test/unit/nedap/utils/speced/predicates.clj

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
(ns unit.nedap.utils.speced.predicates
22
(:require
3+
[clojure.spec.alpha :as spec]
34
[clojure.test :refer :all]
4-
[nedap.utils.spec.predicates :as sut]))
5+
[nedap.utils.spec.predicates :as sut]
6+
[spec-coerce.core :as spec-coerce]))
57

68
(deftest integers
79
(are [v
@@ -68,3 +70,92 @@
6870
"a/a" true
6971
::a true
7072
'a/a true))
73+
74+
(spec/def ::neg-integer? sut/neg-integer?)
75+
76+
(spec/def ::nat-integer? sut/nat-integer?)
77+
78+
(spec/def ::pos-integer? sut/pos-integer?)
79+
80+
(deftest coercion
81+
(testing "Valid values are coerced"
82+
(are [input spec output] (= output
83+
(spec-coerce/coerce spec input))
84+
-1 ::neg-integer? -1
85+
"-1" ::neg-integer? -1
86+
87+
0 ::nat-integer? 0
88+
"0" ::nat-integer? 0
89+
1 ::nat-integer? 1
90+
"1" ::nat-integer? 1
91+
92+
1 ::pos-integer? 1
93+
"1" ::pos-integer? 1))
94+
(testing "Invalid values (as per the predicates themselves) are not coerced"
95+
(are [input spec output] (= output
96+
(spec-coerce/coerce spec input))
97+
nil ::neg-integer? nil
98+
"a" ::neg-integer? "a"
99+
0 ::neg-integer? 0
100+
1 ::neg-integer? 1
101+
"0" ::neg-integer? "0"
102+
"1" ::neg-integer? "1"
103+
104+
nil ::nat-integer? nil
105+
"a" ::nat-integer? "a"
106+
-1 ::nat-integer? -1
107+
"-1" ::nat-integer? "-1"
108+
109+
nil ::pos-integer? nil
110+
"a" ::pos-integer? "a"
111+
-1 ::pos-integer? -1
112+
"-1" ::pos-integer? "-1"
113+
0 ::pos-integer? 0
114+
"1" ::pos-integer? 1))
115+
116+
(testing "Types are preserved"
117+
(are [input spec] (= input
118+
(spec-coerce/coerce spec input))
119+
(Integer. -1) ::neg-integer?
120+
(Long. -1) ::neg-integer?
121+
(clojure.lang.BigInt/fromLong -1) ::neg-integer?
122+
(BigInteger/valueOf -1) ::neg-integer?
123+
(Short. "-1") ::neg-integer?
124+
(Long. -1) ::neg-integer?
125+
126+
(Integer. 0) ::nat-integer?
127+
(Long. 0) ::nat-integer?
128+
(clojure.lang.BigInt/fromLong 0) ::nat-integer?
129+
(BigInteger/valueOf 0) ::nat-integer?
130+
(Short. "0") ::nat-integer?
131+
(Long. 0) ::nat-integer?
132+
133+
(Integer. 1) ::nat-integer?
134+
(Long. 1) ::nat-integer?
135+
(clojure.lang.BigInt/fromLong 1) ::nat-integer?
136+
(BigInteger/valueOf 1) ::nat-integer?
137+
(Short. "1") ::nat-integer?
138+
(Long. 1) ::nat-integer?
139+
140+
(Integer. 1) ::pos-integer?
141+
(Long. 1) ::pos-integer?
142+
(clojure.lang.BigInt/fromLong 1) ::pos-integer?
143+
(BigInteger/valueOf 1) ::pos-integer?
144+
(Short. "1") ::pos-integer?
145+
(Long. 1) ::pos-integer?))
146+
147+
(testing "Values which would overflow a Long are coerced to BigInteger"
148+
(are [input spec expected-class] (= expected-class
149+
(-> (spec-coerce/coerce spec input)
150+
class))
151+
"-1111" ::neg-integer? Long
152+
"-11111111111111111111111111111111" ::neg-integer? BigInteger
153+
(-> Long/MIN_VALUE str) ::neg-integer? Long
154+
(-> Long/MIN_VALUE dec' str) ::neg-integer? BigInteger
155+
"0" ::nat-integer? Long
156+
"1111" ::nat-integer? Long
157+
"11111111111111111111111111111111" ::nat-integer? BigInteger
158+
"1111" ::pos-integer? Long
159+
"11111111111111111111111111111111" ::pos-integer? BigInteger
160+
(-> Long/MAX_VALUE str) ::pos-integer? Long
161+
(-> Long/MAX_VALUE inc' str) ::pos-integer? BigInteger)))

0 commit comments

Comments
 (0)