Fork me on GitHub
#clojure-spec
<
2016-07-01
>
seancorfield03:07:59

I’m working with instants and ended up with this code:

(defn years-ago
  "Given a number of years, return an Instant that long ago."
  [y]
  (-> (LocalDate/now) (.minusYears y) .atStartOfDay (.atOffset ZoneOffset/UTC) .toInstant))

(defn age-18-120?
  "Given an Instant, return true if it represents a date of birth such
  that someone would be between 18 and 120 years old."
  [i]
  (s/inst-in-range? (years-ago 120) (years-ago 18) i))

(s/def ::date-of-birth (s/with-gen age-18-120?
                         (fn [] (s/gen (s/inst-in (years-ago 120) (years-ago 18))))))
Could this be done more cleanly? (without the duplication of the range of years)

bfabry04:07:37

@seancorfield:

(s/def ::age-18-120? (s/inst-in (years-ago 120) (years-ago 18)))
=> :kafka-google-connector.runner/age-18-120?
(defn age-18-120? [x] (s/valid? ::age-18-120? x))
=> #'kafka-google-connector.runner/age-18-120?
(s/def ::date-of-birth ::age-18-120?)
=> :kafka-google-connector.runner/date-of-birth
(s/exercise ::date-of-birth 1)
=> ([#inst"1970-01-01T00:00:00.000-00:00" #inst"1970-01-01T00:00:00.000-00:00"])

bfabry04:07:28

though the age-18-120? fn seems totally redundant at that point, but if you still wanted it

seancorfield04:07:20

No, because the years-ago calls must happen when the predicate is applied, not just once at compile time.

bfabry04:07:50

riiiiight, makes sense

seancorfield04:07:01

And I'm assuming the with-gen generator-returning fn is called each whenever the spec is exercised / test-gen'd but that's not really as important since tests are short-lived, whereas the spec has to be long-lived and check the correct range of dob each time it's conformed / used for validation.

Oliver George09:07:38

@seancorfield: could "now" be part of the data structure you spec/validate? that makes it a simple input rather than implied context.

Oliver George11:07:19

Will it make sense to update defn to allow a spec to be provided as metadata? seems like it could overlap with pre/post...

(defn option-match
  "Default search for local datasource: case-insensitive substring match"
  [simple? option query]
  {:args (s/cat :simple? boolean? :option ::option :query string?)}

Alex Miller (Clojure team)11:07:56

No, we won't be doing that

seantempesta13:07:31

So, specs can also be used to convert invalid data into valid data? I’m reading this…

conformer
macro
Usage: (conformer f)
       (conformer f unf)
takes a predicate function with the semantics of conform i.e. it should return either a
(possibly converted) value or :clojure.spec/invalid, and returns a
spec that uses it as a predicate/conformer. Optionally takes a
second fn that does unform of result of first
So how do you use the feature to get the response (possibly converted)?

bhauman13:07:22

@seantempesta: you have to provide a function that returns :invalid or a result that is the converted value

bhauman13:07:33

(conformer (fn [a] (if (= a "one") 1 :clojure.spec/invalid)))

seantempesta13:07:21

holy crap, this is great!

wilkerlucio15:07:22

@ghadi: one simple option that I see is to move the cached data to the map meta-data, this way the map validation can stay simple

glv15:07:20

But I’d like to validate the cached data as well. For purposes of the code, it’s not valid if those keys aren’t there.

glv15:07:42

(That was clumsily worded, but you know what I mean … the code expects those keys.)

wilkerlucio15:07:09

@glv: I had a different idea, what you think on this:

wilkerlucio15:07:42

here, I used a conformer to remove the namespaced keys before doing the map validation, but checking the keys before doing it

wilkerlucio15:07:58

this way all namespaced keys will be validated, then removed just to check the rest of the map

glv15:07:32

Hmm … that’s better than my current horrific solution. 🙂

seancorfield16:07:54

@olivergeorge: Interesting idea… The data structure comes from the database and we already decorate it with some computed fields that are transient (short-lived) so adding a current timestamp to the data wouldn’t be a hardship.

jjcomer16:07:44

I’m trying to use spec-test/test in my clojure.test tests (that was a lot of test :)). When I try to realize the result of test I get the following exception

java.lang.ClassCastException: clojure.lang.AFunction$1 cannot be cast to clojure.lang.MultiFn, compiling:(clojure/test/check/clojure_test.cljc:95:1)

jjcomer16:07:58

If I run the test outside of a deftest it works no problem

jjcomer16:07:16

Does anyone have a strategy they are using to use spec-test/test within a deftest?

glv17:07:16

@alexmiller no worries … whenever things calm down is fine.

akiel19:07:17

What is the idiomatic way to spec an empty :args seq? I currently use (s/cat).