Fork me on GitHub
#clojure-spec
<
2022-04-07
>
didibus03:04:34

I find I often have this scenario:

{:type :foo
 :bar 1
 :baz 2}
Where I want my spec to say that the keys are :type, :bar and :baz, but also that the value of :type must be :foo. What's the most straightforward way to write that spec? Bonus point where the generator for it properly constructs a map where :type is always :foo.

Alex Miller (Clojure team)04:04:51

have you looked at multi-spec?

Alex Miller (Clojure team)04:04:24

seems like it would be a great match here

didibus05:04:51

Multi-spec is actually where I have this problem, because the generator doesn't generate the correct :type

didibus05:04:29

Like if we take the spec guide example: https://clojure.org/guides/spec#_multi_spec Assume that instead of: (s/def :event/type keyword?) we have (s/def :event/type #{:event/search :event/error}) If you try to generate an :event/event, they won't be valid to any of the type of events most of the time, because the generator doesn't know which of the :event/type to pick for the different multi-specs

flowthing07:04:15

I'd like to write a spec for a clojure.data.xml data structure and use said spec for generation as well. For example, say I have something like this:

{:tag :foo
 :content [{:tag :bar
            :content ["..."]}]}
I'm not sure what the best way to do that would be, though. That is, how do I write (spec/def ::foo ,,,) that specifies that :tag needs to be :foo and :content must conform to the ::bar spec, etc. Is some combination of spec/keys and spec/and the way to go?

lassemaatta08:04:16

It's been a while since I've used spec so this might be horribly wrong, but it might also solve both your problems 😃 🧵

flowthing08:04:31

Thank you! I'll give that a go. I tried flailing about with multi-spec a bit, but I didn't think of the merge into a base element spec, that's clever. :thumbsup:

lassemaatta08:04:33

I tested this for at least two seconds, so caveat emptor 🙂

flowthing08:04:42

Sure thing, no worries. 🙂

flowthing09:04:47

Actually, it's probably easier to write a spec for the Hiccup syntax using tuples and/or regexp ops.

flowthing09:04:01

(spec/def ::messageId
  (spec/tuple #{:messageId} string?))

(spec/def ::from
  (spec/tuple #{:from} string?))

(spec/def ::soap/Header
  (spec/tuple #{::soap/Header}
    ::messageId
    ::from
    ;; etc
    ))

(spec/def ::soap/Envelope
  (spec/tuple #{::soap/Envelope}
    ::soap/Header))

(->
    (spec/gen ::soap/Envelope)
    (gen/generate)
    (xml/sexp-as-element)
    (xml/emit-str)
    (pretty-print-xml-string)
    (println))

;;=>
<a:Envelope xmlns:a="">
  <a:Header>
    <messageId>I67NC7</messageId>
    <from>733R0T1MGsY9cnx943</from>
  </a:Header>
</a:Envelope>

👍 1
🧼 1
lassemaatta15:04:56

@U0K064KQV the example I gave above might offer one way to use generators with multi-specs. but as I said, I'm no expert in this stuff so there may be better ways to do it