clojure-spec 2024-03-08

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+"}}}

1

Thanks for being my rubber duck!

Alex Miller (Clojure team) 2024-03-08T19:10:15.336899Z

I think maybe s/alt instead of s/or may also have been a better path

Alex Miller (Clojure team) 2024-03-08T19:10:42.219569Z

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.