Fork me on GitHub
#clojure-spec
<
2023-02-15
>
stopa18:02:28

Hey team, noob question. Consider the following object:

(s/def ::type #{:foo :bar}) 
(s/def ::obj (s/keys :req [::type ::a ::b] :opt [::c]))
How would I express something like: If ::type is :foo, ::c is required, but otherwise it's not?

Alex Miller (Clojure team)19:02:06

you can s/and a function that tests any property you like

stopa03:02:30

Hey @U064X3EF3 one more question:

(s/def ::type #{:biz :baz})
(s/def ::foo string?)
(s/def ::bar string?)
(s/def ::kux string?)
(s/def ::common (s/keys :req [::type ::foo ::bar]))
(s/def ::biz-obj ::common)
(s/def ::baz-obj
  (s/merge ::common
           (s/keys :req [::kux])))

(defmulti type-mm ::type)
(defmethod type-mm :biz [_] ::biz-obj)
(defmethod type-mm :baz [_] ::baz-obj)

(s/def ::obj (s/multi-spec type-mm ::type))

(gen/generate (s/gen ::obj)))
Here I make it so if the type is :baz, then ::kux is required. But one thing I did want to do: Sometimes I want to generate the different instances of the objects -- i.e ::biz-obj and ::baz-obj But the way I wrote it, I would get an invariant. If I wrote:
(gen/generate (s/gen ::biz-obj)))
I would sometimes get values with ::type :baz This is because I don't constraint the type in this layer. I guess I have two questions: Is this the right approach? And if so, how would I go about properly constructing ::biz-obj and ::baz-obj?

Alex Miller (Clojure team)17:02:10

well you could constrain the type in those layers

Alex Miller (Clojure team)17:02:33

or make a generator that inserted the expected type

stopa15:02:27

One question for my learning: How would I constrain the type in those layers?

(s/def ::biz-obj (s/keys :req [::type ::foo ::bar]))

(s/def ::baz-obj
  (s/merge ::biz-obj
           (s/keys :req [::kux])))
Since these are both s/keys, and they both must take ::type as one of the validations, how would I say "for this map, ::type is just this part"? Or did you have something else in mind?