I'm struggling with Clojure(script) spec. I slightly found out what part causes problem but I can't solve it.
(defn filter-ids
[[items fields] _]
(let [ids
(for [item items
field-tags (vals fields)
:let [item-tags (-> item second :tags)
item-id (first item)]
:when (and
(seq field-tags)
(empty? (set/difference field-tags item-tags)))]
item-id)]
(into #{} ids)))
Above code is what I tried to define spec. (fdef)
And I defined spec.
(spec/def :common/id (spec/and
keyword?
#(-> %1 name js/parseInt nat-int?)))
(spec/def :common/label string?)
(spec/def :common/tags (spec/coll-of string? :kind set?))
(spec/def :common/item (spec/keys :req-un [:common/label :common/tags]))
(spec/fdef filter-ids
:args (spec/cat
:useful (spec/cat
:items (spec/map-of :common/id :common/item)
:fields (spec/map-of :common/id :common/tags))
:useless any?)
:ret (spec/coll-of :common/id :kind set?))
And when I run it with instrument, error occurs.
(stest/instrument `filter-ids)
(filter-ids [{:0 {:label "task0" :tags #{"one" "two"}}}
{:0 #{"three"}, :1 #{"one"}}]
nil)
; Execution error - invalid arguments to taggy.states.subs/filter-ids at (<cljs repl>:1).
[{:0 {:label "task0", :tags #{"two" "one"}}} {:0 #{"three"}, :1 #{"one"}}] - failed: map? at: [:useful :items]
It seems like spec think first argument needs to be map, which is what I'm not intended to.
When I do like below, it doesn't complaining about map?. (although still a error because it's not valid at all)
(filter-ids {{:0 {:label "task0" :tags #{"one" "two"}}} 1
{:0 #{"three"}, :1 #{"one"}} 2}
nil)
I'm a newbie and really need some help to move on.
Thanks.
spec/cat
is a "sequence regex" and it "unrolls" if you nest it inside another spec/cat
.
You can either wrap the inner spec/cat
call in a spec/spec
call, which prevents that unrolling, or you can switch to spec/tuple
(and remove the :items
and :fields
labels):
(spec/fdef filter-ids
:args (spec/cat
:useful (spec/spec (spec/cat
:items (spec/map-of :common/id :common/item)
:fields (spec/map-of :common/id :common/tags)))
:useless any?)
:ret (spec/coll-of :common/id :kind set?))
;; or
(spec/fdef filter-ids
:args (spec/cat
:useful (spec/tuple
(spec/map-of :common/id :common/item)
(spec/map-of :common/id :common/tags))
:useless any?)
:ret (spec/coll-of :common/id :kind set?))
Both of those will work. Which you choose may depend on what information you want in your error messages (I think the former provides more context when you get something wrong because of the :items
and :fields
labels).