clojure-spec

2023-02-15T18:58:28.852939Z

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) 2023-02-15T19:00:06.230779Z

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

Alex Miller (Clojure team) 2023-02-15T19:00:48.211129Z

but you might also want to look at s/multi-spec https://clojure.org/guides/spec#_multi_spec

2023-02-15T19:01:59.055699Z

Nice! Thanks @alexmiller!

2023-02-16T03:18:30.786619Z

Hey @alexmiller 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) 2023-02-16T17:29:10.247919Z

well you could constrain the type in those layers

Alex Miller (Clojure team) 2023-02-16T17:29:33.563969Z

or make a generator that inserted the expected type

Alex Miller (Clojure team) 2023-02-16T17:29:44.297259Z

using gen/fmap

2023-02-18T15:11:27.515109Z

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?