I'm working on retroactively defining specs for Pedestal routes; mostly it's worked well, but I've hit my limit trying to spec a table route.
A table route looks like ["/" :get my-handler :route-name ::my-handler]
The first three parts are straight forward, bit there are these clauses (:route-name <keyword>) and (:constraints <map>) that can optionally follow the fixed part of the table route, in any order.
My initial pass:
(s/def ::table-route
(s/cat
:path ::path
:verb keyword? ;; This should be constrained based on options
:handler ::table-handler
:clauses (s/* ::table-route-clause)))
(s/def ::table-handler
(s/or
:symbol symbol?
:interceptor ::i/interceptor
:interceptors ::i/interceptors))
(s/def ::table-route-clause
(s/or
:route-name ::table-route-name-clause
:interceptors ::table-interceptors-clause))
(s/def ::table-route-name-clause
(s/cat :k #{:route-name}
:route-name ::route-name))
(s/def ::table-interceptors-clause
(s/cat :k #{:interceptors}
:interceptors ::i/interceptors))
But that isn't working, and I get:t
#{[... ... ... :route-name ...]
^^^^^^^^^^^
["/users/:id"
:get
[io.pedestal.http.route-test/view-user]
:route-name
:io.pedestal.http.route-test/view-user
:constraints
{:id #"\d+"}]}
should satisfy
(fn
[%]
(or (nil? %) (sequential? %)))
Any pointers here?I think I found it, s/keys*.
(s/def ::table-route
(s/cat
:path ::path
:verb keyword? ;; This should be constrained based on options
:handler ::table-handler
:clauses (s/keys* :opt-un [::route-name ::constraints])))
(s/conform ::specs/table-route
["/users/:id" :get [`view-user] :route-name ::view-user :constraints {:id #"\d+"}])
=>
{:path "/users/:id",
:verb :get,
:handler [:interceptors [io.pedestal.http.route-test/view-user]],
:clauses {:route-name :io.pedestal.http.route-test/view-user, :constraints {:id #"\d+"}}}Thanks for being my rubber duck!
I think maybe s/alt instead of s/or may also have been a better path
keys* is like "& args" - it allows any number at the end, not sure if that's what you want
Perhaps you can look at the Pedestal PR when it's ready? I'm kind of in the camp of "it works, all tests pass". I actually like the idea that there can be extra options beyond :route-name and :constraints, so I may integrate that and document it.