Here is my clojure spec written for hiccup like syntax.
(:require
[clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as st]
))
(s/def ::tag (s/and
keyword?
#(re-matches #":[a-z]+([0-9]+)?"
(str %))))
(s/def ::org-content (s/cat
:tag ::tag
:content (s/+ (s/or
:str string?
:content ::org-content
))))
I wrote a simple function spec here -
(s/fdef org-headers-h3
:args (s/cat :contact ::org-content)
:ret keyword?)
(defn org-headers-h3 [doc]
(first doc))
(st/instrument `org-headers-h3)
(org-headers-h3 [:div [:h1 "d"]])
There leads the following error -
*Call to #'modcss2.parser/org-headers-h3 did not conform to spec:
In: [0] val: [:div [:h1 "d"]] fails spec: :modcss2.parser/tag at:
[:args :contact :tag] predicate: keyword? :clojure.spec.alpha/spec
#object[clojure.spec.alpha$regex_spec_impl$reify__1200 0x6613f384
"clojure.spec.alpha$regex_spec_impl$reify__1200@6613f384"]
:clojure.spec.alpha/value ([:div [:h1 "d"]])
:clojure.spec.alpha/args ([:div [:h1 "d"]])
:clojure.spec.alpha/failure :instrument*
I seem to me, I am getting error for passing wrong type of argument. but I am getting true for following statement.
(s/valid? ::org-content [:div [:h1 "d"]]) => true
One s/cat
inside another doesn't mean they're nested, they just concatenate. This goes for all the regex specs.
(s/conform (s/cat :foo (s/cat :bar int?)) [1]) ;=> {:foo {:bar 1}}
(s/conform (s/cat :foo (s/cat :bar int?)) [[1]]) ;=> ::s/invalid
Your :args
spec expands to something like: (s/cat :contact (s/cat :tag ::tag ,,,))
To nest them you can use s/spec
.
(s/conform (s/cat :foo (s/spec (s/cat :bar int?))) [[1]]) ;=> {:foo {:bar 1}}
All regex specs concatenate like that. In this case it makes sense to use s/and
to both check that it's a vector and make it a non-regex spec so it nests normally.
(s/conform (s/cat :foo (s/and vector? (s/cat :bar int?))) [[1]]) ;=> {:foo {:bar 1}}
So, to fix your problem:
(s/def ::org-content
(s/and vector?
(s/cat
:tag ::tag
:content (s/+ (s/or
:str string?
:content ::org-content)))))