Clojure.spec: Conforming values

I am looking for ways to use spec to control the evolution of an API we don’t control.

Say we have an API like:

{:username "abc"
 :signed-up "2018-02-15" }

That at some point becomes:

{:username "abc"
 :sign-on {:yy 2018 :mm 2 ::dd 15}}

And I need to handle both.

Writing a spec to know whether either is a valid form is trivial; this said, once I know that it’s either one or the other, I need to handle both in code.

So I was thinking that maybe s/conform was what I was looking for: a way to reduce one form to the other, so that my code only has to deal with a canonical, spec-validated input form. But how do I do this? or is it the wrong tool for the job?

TIA

Create an s/or spec of the two.

Now call conform and it will return you the tag of the or that matched. So you know if you have a case of the first or the second.

Create a function that converts one to the other. When conform says the data is of the other tag you expect, then convert it with your conversion function.

5 Likes

Yes, this is exactly what conform is for!

As didibus mentioned make an “or” spec with both variants, e.g. ::string-data and ::map-date.
I used a multimethod to dispatch on the returned label

(defmulti convert-date first)  ;; dispatch on "first" which is the label returned by conform, either ::string-data or ::map-date

So say you like the map version of the date better, then

(defmethod convert-date ::map-date [date] date)  ;; do nothing
(defmethod convert-date ::string-date [date] (parse date))

You might also be able to parse the string date with spec. But that’s a little more work to get right (in my experience).

I have posted this here before, but that case is very similar to your problem: https://feierabendprojekte.wordpress.com/2016/09/11/generating-ui-for-arbitrarily-nested-data-structures/

cheers

Edit: realized that the keyword is also different (::signed-up vs ::sign-on). The same technique will also work, but you’ll have to dispatch on the type of the outer map.

2 Likes