Fork me on GitHub
#clojure-spec
<
2017-01-23
>
mrg00:01:15

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

mrg00:01:29

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

mrg00:01:03

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

mrg00:01:37

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

seancorfield00:01:05

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

mrg00:01:38

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

mrg00:01:46

I don't really like it either

mrg00:01:19

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

seancorfield00:01:24

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

mrg00:01:30

Quite likely

mrg00:01:56

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

mrg00:01:11

1) works mostly, using generators and gen/generate

mrg00:01:29

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

mrg00:01:02

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

mrg00:01:20

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

seancorfield00:01:04

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."
  [s]
  (if (instance? java.util.Date s)
    s
    (-> (tf/formatter t/utc "yyyy/MM/dd" "MM/dd/yyyy"
                      "EEE MMM dd HH:mm:ss zzz yyyy")
        (tf/parse s)
        (tc/to-date))))

(defn ->date
  "Spec predicate: conform to Date else invalid."
  [s]
  (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?))

seancorfield00:01:11

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.

mrg00:01:19

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

mrg00:01:32

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

rickmoynihan13:01:37

@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.

rickmoynihan13:01:51

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

rickmoynihan13:01:07

obviously you can also use instrument

andrewzhurov13:01:22

I just find out that lein test is giving that wierdo error and refind-out for myself this issue: https://github.com/technomancy/leiningen/issues/2173 turned off monkeydispatch and whoop - all is good.

andrewzhurov13:01:13

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

lsnape17:01:00

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

souenzzo20:01:56

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}

seancorfield20:01:40

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

seancorfield20:01:15

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.

seancorfield20:01:26

(but you probably shouldn’t do that)