Reputation: 5500
If I have defined the following record:
(defrecord Person [name id])
and the following:
(s/def ::name string?)
(s/def ::id int?)
(s/def ::person (s/keys :req-un [::name ::id]))
How can I ensure that you can't create a Person that does not conform to the ::person spec? In other words, the following should throw an exception:
(->Person "Fred" "3")
I tried:
(s/fdef ->Person :ret ::person)
but calling:
(->Person "Fred" "3")
does not raise an exception.
However:
(s/conform ::person (->Person "Fred" "3"))
does yield the expected:
:clojure.spec/invalid
Thanks
Upvotes: 6
Views: 1905
Reputation: 70239
fdef
:ret and :fn specs are only checked during clojure.spec.test/check
tests, but you could use an fdef :args spec to check the inputs to the constructor function when instrumented.
(s/fdef ->Person
:args (s/cat :name ::name :id ::id)
:ret ::person)
(require '[clojure.spec.test :as stest])
(stest/instrument `->Person)
(->Person "Fred" "3")
=> CompilerException clojure.lang.ExceptionInfo: Call to #'spec.examples.guide/->Person did not conform to spec:
In: [1] val: "3" fails spec: :spec.examples.guide/id at: [:args :id] predicate: int?
:clojure.spec/args ("Fred" "3")
:clojure.spec/failure :instrument
:clojure.spec.test/caller {:file "guide.clj", :line 709, :var-scope spec.examples.guide/eval3771}
It wouldn't be too hard to macro the combination of defrecord and fdef of the constructor using the matching specs.
Upvotes: 12