I’ve been experimenting with clojure.spec on a new web project, as a way of validating data coming in from the client. We’re using JSON API as the lingua franca between the server and client, which adds a not-insignificant amount of ceremony and overhead to the JSON payload. So once I got one namespace’s endpoints spec’ed correctly, the obvious next step was to generalize it with a function that looked like this (edited for brevity):
What we’re accomplishing here with
(keyword api-ns "attributes") is building a keyword that looks like
:my-project.api.users/attributes. Or at least, that’s what I thought we were doing; here’s the error I ran into:
CompilerException java.lang.AssertionError: Assert failed: k must be namespaced keyword or resolvable symbol
That’s weird —
(keyword api-ns "attributes") on the REPL is building a
clojure.lang.Keyword like I expected it to, so why is an assert that it’s a keyword failing?
The answer lies in how the macro behind
clojure.spec/def is implemented. Macroexpanding
(s/def (keyword "spec-test-ns" "name") string?) gives you:
So since it’s
quoteing our input, it’s not resolving to the keyword before the assert. The solution I came to is to build a macro that unquotes my keyword building before
s/def is called:
I generally prefer non-macro solutions when available, but as of now, this seems like the only way to DRY up this particular pattern.