Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Spec by

Hi,

I'm pretty new to Clojure and recently got interested in using spec. One use-case I'm interested in, is to use it for validation of API inputs. As far as I understood I have the following possibilities to correctly declare / use specs in that case:

  1. Create specs with unqualified names and use plain keywords in API input. I guess that's prone to error as one can think of overlapping specs with different requirements (e. g. having IDs with different scheme in two entities).

  2. Create specs with qualified names and also use qualified names in API input. That would work as expected but feels like unnecessary extra work on the client side as all clients now need to specify the namespace for all keys while its usually clear which entity is expected (e. g. API endpoint /customer-address is expecting a customer address but still all fields need to be namespaced in the input like :customer-address/street).

  3. Create specs with qualified names but use :req-un instead of :req to specify required keys in specs. Works as expected unless someone tries to validate a map with qualified names (shouldn't usually happen in API requests but might occur within the application where namespaces might not be a bad idea).

  4. Create specs with qualified names and lift all keywords in API inputs into the expected namespace. Would work as expected and has the benefit that namespaced keywords can be used in the application. Drawback is that every API input needs to be transformed before validation can happen.

So, my question is, what is the ideomatic way to validate API input with spec?
And maybe as a bonus question: why is spec using the global "database" for specs? It looks like a lot of problems/confusion arise from that design decision (e. g. name clashes / overrides) and it also doesn't look like a very "functional" way. To clarify, I'm also not an expert in functional programming, so would be great if someone could highlight the benefits that led to that decision.

Best
Ben

1 Answer

0 votes
by

For input validation I don't think you should let spec drive your API, and I think you should minimize/avoid transformation for the purposes of validation. So if you want unqualified keys in your API, then I would consider #3 as the preferred way here (doesn't change your API, doesn't transform needlessly).

If you transform the data and validate on the transformed data, then the errors and error data relate to the transformed data, not the originals provided by the user, which seems like it would be confusing.

For the why questions - see https://clojure.org/about/spec which is intended to answer that.

by
Thanks for giving direction regarding the way to use and the page mentioned.

Reading through the about page, I still don't understand the exact reason to go with a global store/db for specs.
The page states:
> Specs for data structures, attribute values and functions should all be the same and live in a globally-namespaced directory.

But it doesn't clarify why specs should live in a globally-namespaced directory.

However, I don't want to challenge the implementation, as I think overall its a great tool. Just wanted to understand why its built that way.

With the current implementation, is there a good reason why map keys declared with :req-un do not allow qualified names in addition to unqualified ones (as that would further simplify my case). One could argue that a :req-un [ :id ] requires an :id keyword to be present. So it could also be ok to have a "more qualified" version as ":user/id" which is also referring to an id.

As an added benefit that could "lift" map keywords to a certain namespace when using conform:

=> (s/conform :qualified/user {:id "some-id".  ..})
      { :qualified/user/id "some-id" }

Does that make sense?

Thanks for sharing your view!
...