clojure-spec

hlship 2024-03-08T18:15:44.097259Z

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?

hlship 2024-03-08T18:28:57.916829Z

I think I found it, s/keys*.

hlship 2024-03-08T18:30:08.770139Z

(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
hlship 2024-03-08T18:33:06.206489Z

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

hlship 2024-03-08T22:44:28.475109Z

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.