Fork me on GitHub

Thanks @olivergeorge and @seancorfield . I was basing that off a comment of Timothy Baldridge on reddit that was suggesting that conformer was the way to go if one wanted to use a spec to coerce data


What would be the right way to do something like that then though?


Say I have a spec (s/def ::myspec #{:foo :bar})


and then i have a deeply nested json map that at multiple places uses ::myspec


Well, yes, s/conformer is the way to go -- but it's a "concern" to have a spec do data transformation.


sorry, there would also be (s/def ::myspecs (s/coll-of ::myspec :min-elements 1 :max-elements 5)) or something like that


I don't really like it either


But I can't think of a better way that doesn't have me re-implement half of spec for every branch of the tree


I'm not sure what you're asking -- and I think you're misunderstanding what we're saying...?


Quite likely


My actual use case is this: I have a service that 1) generates random data that conforms to a spec and spits it out as json 2) is supposed to take such a json map and check its conformance to the spec


1) works mostly, using generators and gen/generate


My problem is in step #2 when I try to read the generated json back in


the uuids have become strings, the enums (like the ::myspec) above as well, and so have the #inst datetimes


And I can't figure out how to coerce them back into the format that my spec expects


Here's an example of some specs etc we use that accept strings and coerce them to dates:

(defn coerce->date
  "Given a string or date, produce a date, or throw an exception.
  Low level utility used by spec predicates to accept either a
  date or a string that can be converted to a date."
  (if (instance? java.util.Date s)
    (-> (tf/formatter t/utc "yyyy/MM/dd" "MM/dd/yyyy"
                      "EEE MMM dd HH:mm:ss zzz yyyy")
        (tf/parse s)

(defn ->date
  "Spec predicate: conform to Date else invalid."
  (try (coerce->date s)
       (catch Exception _ ::s/invalid)))

(defmacro api-spec
  "Given a coercion function and a predicate / spec, produce a
  spec that accepts strings that can be coerced to a value that
  satisfies the predicate / spec, and will also generate strings
  that conform to the given spec."
  [coerce str-or-spec & [spec]]
  (let [[to-str spec] (if spec [str-or-spec spec] [str str-or-spec])]
    `(s/with-gen (s/and (s/conformer ~coerce) ~spec)
       (fn [] (g/fmap ~to-str (s/gen ~spec))))))

(s/def ::dateofbirth (api-spec ->date #(dt/format-date % "MM/dd/yyyy") inst?))


This accepts an instant or a string that will coerce to an instant. It auto-generates as well based on inst? and converts it to a MM/dd/yyyy string.


hey, thank you @seancorfield , that is super useful!


And I appreciate you helping me out on a Sunday evening 🙂


@brownmoose3q: Interesting… I’ve asked similar questions before, but never had any responses… I’m not sure if it’s because there isn’t an answer for it yet… I’d certainly like to know how to integrate clojure.spec with a clojure.test runner.


not sure what others do… but might be nice to have some clojure.test wrappers for tools like exercise etc...


obviously you can also use instrument


I just find out that lein test is giving that wierdo error and refind-out for myself this issue: turned off monkeydispatch and whoop - all is good.


So... seems to be a way of testing 🙂


Can anyone suggest how to simplify the following:

(s/def ::created inst?)
(s/def ::updated inst?)
(s/def ::foo* (s/keys :req-un [::created ::updated]))

(s/def ::foo
  (s/with-gen ::foo*
    #(gen/such-that (fn [{:keys [created updated]}]
                      (>= (.getTime updated) (.getTime created)))
                    (s/gen ::foo*))))

(s/exercise ::foo)
I would like to ditch ::foo*, but if I reference the ::foo generator from within s/def ::foo, then understandably I get a SOE


There is option in s/keys that fails/remove on unwanted keys?? (s/valid? (s/keys :req [:a/b]) {:a/b 0 :c/d 1}) ;=> false or something like (s/"filter????" (s/keys :req [:a/b]) {:a/b 0 :c/d 1}) ;=> {:a/b 0}


@souenzzo spec is designed to be open for extension — read the guide for more details.


If you want a spec to fail for unwanted keys, you need to explicitly s/and a predicate that checks the set of keys in the map.


(but you probably shouldn’t do that)