Reputation: 4582
Consider the following spec for a text or a link layer port number:
(require '[clojure.spec.alpha :as spec])
(spec/def ::text (spec/and string? not-empty))
(spec/valid? ::text "a") ; => true
(spec/valid? ::text "") ; => false
(spec/def ::port (spec/and pos-int? (partial > 65535)))
(spec/valid? ::port 4) ; => true
(spec/valid? ::port 0) ; => false
(spec/def ::text-or-port (spec/or ::text ::port))
(spec/valid? ::text-or-port 5) ; => true
(spec/valid? ::text-or-port "hi") ; => false
For some reason it only accepts port-numbers and not text, why would that be?
Upvotes: 1
Views: 111
Reputation: 4582
The key to understanding this problem can be found in in the documentation and using spec/conform
.
(spec/conform ::text-or-port 5)
; => [:user/text 5]
The problem is that clojure.spec.alpha/or
has an API which is dissimmilar to clojure.core/or
which given two arguments returns the first truthy one:
(#(or (string? %) (integer? %)) 5) ; => true
(#(or (string? %) (integer? %)) "") ; => true
(#(or (string? %) (integer? %)) :a) ; => false
Rather it takes pairs of labels and specs/predicates. And since even namespaced keywords are accepted as labels the ::text-or-port
spec given in the OP matched only that which passed the requirements for ::port
and gave it the label ::text
. Below is a correct spec for that which we want to match:
(spec/def ::text-or-port (spec/or :text ::text
:port ::port))
(spec/valid? ::text-or-port "hi") ; => true
(spec/valid? ::text-or-port 10) ; => true
Upvotes: 2