Clojure spec and record constructors
Asked Answered
S

1

6

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

Sanbo answered 22/7, 2016 at 3:47 Comment(0)
C
12

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.

Coniine answered 22/7, 2016 at 4:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.