Fork me on GitHub
#clojure-spec
<
2020-09-26
>
johanatan02:09:40

has anyone given much thought to (for the purposes of using multi-spec liberally across many namespaces) dynamically constructing "qualified keywords" at reader / compile time ?

johanatan02:09:51

(in clojurescript)

johanatan02:09:16

i've attempted both a plain macro and a tagged literal reader

johanatan02:09:36

this is the macro:

(defmacro ns-grouped-keyword [group keyword]
  `(keyword (clojure.string/join "/" [~(str *ns*) ~group]) ~keyword))

johanatan02:09:53

and this is the reader:

(defn read-ns-grouped-keyword [s]
  `(let [[group# kwd#] (clojure.string/split ~s #"/")]
    (keyword (clojure.string/join "/" [~(str *ns*) group#]) kwd#)))

johanatan02:09:22

even the latter apparently only gets expanded and not eval'd at reader time

johanatan02:09:42

such that: (s/keys :opt-un [#my/reader "blah/blaz"]) will not work

johanatan02:09:57

where as I had expected it to get eval'd to: (s/keys :opt-un [:the-current-namespace/blah/blaz]) before keys expansion time

udit15:09:19

I am trying to use clojure.spec for validating the inputs to my API handlers. What’s the idiomatic pattern for usage here? I was thinking of creating a generic fn which takes in spec and the data to be validated. This fn can throw an exception when the validation fails. This fn would be called as the very first thing in the handlers, thus if the data is not valid the exception will act like an early exit. But this is using exceptions for control flow. The other approach I can think of is wrapping all my handlers with an if-else block that do this validation. This is fine, except it sort of makes my handlers less readable. What’s a clean approach that people have discovered for validating (and coercing) data using spec.

Jeff Evans15:09:00

Can’t you have a single wrapper fn that basically does

(let [res (s/conform input)]
  (if (:s/invalid? res)
    (build-error-reponse)
    (real-handler input) ; real-handler could be an input to this fn
  )
)
Then the real-handler doesn’t have to worry about whether the input is valid, and your outermost layer (i.e. the one that first receives the request) can call this instead of real-handler directly. And if you want more details out of build-error-response you could use explain-str or explain-data instead of conform. Or perhaps I’m misunderstanding the situation.

udit16:09:54

This works @jeffrey.wayne.evans! Thanks! Although I would like to have different spec for different handlers, but I presume that can be passed in as a parameter to this fn a well, right?

Jeff Evans16:09:07

Sure! You would still have to somehow organize the specs per handler. Not sure if there is an established pattern for that but you could always have a keyword based map for them (handler function name as key?)

ikitommi16:09:12

reitit has a spec-module, you can declare request & response specs to endpoints, and there is a set of predefined middleware / interceptors to use them for validation, also coercion. Example here: https://github.com/metosin/reitit/blob/master/examples/ring-spec-swagger/src/example/server.clj

👍 1