Fork me on GitHub
#clojure-spec
<
2020-03-24
>
Quest01:03:06

Specific question about the "data specs" feature in spec-tools: https://github.com/metosin/spec-tools/blob/master/docs/02_data_specs.md I want to specify a vector of elements where the vector's count = 2. Is this possible to specify using data-specs?

seancorfield01:03:01

@quest You can certainly specify that with clojure.spec. No idea about spec-tools.

Quest01:03:00

Yeah, the :min-count option works fine in normal specs... I'm trying to cast to clojure specs from an EDN business schema I have, proving difficult

Quest01:03:33

I know spec2 has much improved support for runtime specs, but I need the CLJS side unfortunately

seancorfield01:03:00

Ah, so you can't eval either...

Quest02:03:11

Just to follow up on what I ended up doing: Creating a separate "pre-runtime" step which generates a file of clojure.spec definitions from the data model. I expect this can be eliminated once spec2 is usable everywhere.

seancorfield02:03:12

Sounds like a good compromise for now!

Quest01:03:04

Yeah. I had some success by doing funky things using the spec private "impl" functions, but it completely mangles the error messages when data fails validation

yonatanel18:03:39

Is it possible to spec a map where either a namespaced key or another unnamespaced key is required? e.g {:my/a 1} ok, {:my-a 1} ok, {:my/a 1, :my-a 2} should also be fine, {:b 2} invalid.

colinkahn20:03:31

Spec V1 doesn’t really have a way to say “this map can’t contain :b”. For aliasing :my-a to :my/a you could choose a single “canonical” version of your map and use a conformer when you expect there to be variations of it.

(s/def :my/a number?)
(s/def ::m (s/keys :req [:my/a]))

(s/def ::->normalize-keys (s/conformer #(clojure.set/rename-keys % {:my-a :my/a})))

(s/valid? (s/and ::->normalize-keys ::m) {:my-a 1})   ;-> true
(s/valid? (s/and ::->normalize-keys ::m) {:my/a 1})   ;-> true
(s/valid? (s/and ::->normalize-keys ::m) {:my-a "1"}) ;-> false
(s/valid? (s/and ::->normalize-keys ::m) {:my/a "1"}) ;-> false
Depending on what your needs are for generation though this can be troublesome.

yonatanel20:03:22

Yeah, it’s nice, but I wanted (s/explain) to mention the unnamespaced key if both are missing

colinkahn20:03:05

@U1B0DFD25 this is kind of convoluted but:

(s/def :my/a number?)
(s/def ::my-a :my/a)

(s/def ::m (s/merge (s/nonconforming (s/or :v1 (s/keys :req [:my/a])
                                           :v2 (s/keys :req-un [::my-a])))
                    (s/keys :opt [:my/a]
                            :opt-un [::my-a])))

(s/valid? ::m {:my/a 1}) ; true
(s/valid? ::m {:my-a 1}) ; true
(s/valid? ::m {:my/a 1 :my-a 2}) ; true
(s/valid? ::m {:b 2}) ; false

(s/explain-data ::m {:b 2})
(s/exercise ::m)

yonatanel20:03:55

@U0CLLU3QT Thanks. I wasn’t aware of nonconforming. Gave me some options anyway.

kszabo18:03:29

not directly

kszabo18:03:55

you have to spec that with additional predicates. (s/keys) doesn’t have support for that AFAIK across req/req-un groups

ag21:03:46

how do I make a (s/coll-off ::foo), making sure that the elements are distinct by one given field? e.g.: if (s/def ::foo (s/keys :req-un [:name])), I want that all the names in the generated collection are unique.

ag21:03:59

nevermind… figure it out… used: (s/and (s/coll-of ,,,, ) #(apply distinct? (mapv :name %)),,,

Quest02:03:11

Just to follow up on what I ended up doing: Creating a separate "pre-runtime" step which generates a file of clojure.spec definitions from the data model. I expect this can be eliminated once spec2 is usable everywhere.